React与Vue中的重排重绘优化策略

React与Vue中的重排重绘优化策略

一、React中的重排重绘优化

1. 组件级渲染优化

(1) React.memo与props比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 防止不必要的重新渲染
const ExpensiveComponent = React.memo(({ data, onUpdate }) => {
console.log('ExpensiveComponent rendered'); // 仅在props真正变化时打印

return (
<div className="expensive-content">
<h3>{data.title}</h3>
<p>{data.description}</p>
<button onClick={() => onUpdate(data.id)}>Update</button>
</div>
);
}, (prevProps, nextProps) => {
// 自定义比较逻辑,只比较关键属性
return prevProps.data.id === nextProps.data.id &&
prevProps.data.version === nextProps.data.version;
});

// 使用useCallback稳定函数引用
const ParentComponent = () => {
const [items, setItems] = useState([]);
const [filter, setFilter] = useState('');

// 稳定的回调函数,避免子组件因引用变化而重渲染
const handleUpdate = useCallback((id) => {
setItems(prev => prev.map(item =>
item.id === id ? { ...item, updated: Date.now() } : item
));
}, []); // 空依赖数组确保引用稳定

const filteredItems = useMemo(() => {
return items.filter(item => item.name.includes(filter));
}, [items, filter]);

return (
<div>
{filteredItems.map(item => (
<ExpensiveComponent
key={item.id}
data={item}
onUpdate={handleUpdate}
/>
))}
</div>
);
};

(2) useMemo缓存计算结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const DataVisualization = ({ rawData, config }) => {
// 避免每次渲染都进行昂贵的数据处理
const processedData = useMemo(() => {
console.time('Data processing');
const result = rawData
.filter(item => item.value > config.minValue)
.map(item => ({
...item,
processedValue: item.value * config.multiplier
}));
console.timeEnd('Data processing');
return result;
}, [rawData, config.minValue, config.multiplier]);

// 缓存复杂的样式对象
const chartStyle = useMemo(() => ({
width: '100%',
height: '400px',
position: 'relative',
contain: 'layout paint' // 限制重排重绘范围
}), []);

return (
<div style={chartStyle}>
<ChartComponent data={processedData} />
</div>
);
};

2. 列表渲染优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 虚拟滚动实现 - 优化大数据列表渲染
const VirtualList = ({ items, itemHeight = 50 }) => {
const containerRef = useRef(null);
const [visibleRange, setVisibleRange] = useState({ start: 0, end: 0 });

// 防抖滚动处理,减少重排频率
const handleScroll = useMemo(() =>
debounce((e) => {
const scrollTop = e.target.scrollTop;
const containerHeight = containerRef.current.clientHeight;

const start = Math.floor(scrollTop / itemHeight);
const visibleCount = Math.ceil(containerHeight / itemHeight);
const end = Math.min(start + visibleCount + 2, items.length); // +2缓冲区

setVisibleRange({ start, end });
}, 16), // 约60fps的防抖时间
[itemHeight, items.length]
);

const visibleItems = items.slice(visibleRange.start, visibleRange.end);

return (
<div
ref={containerRef}
className="virtual-list-container"
onScroll={handleScroll}
style={{
height: '400px',
overflow: 'auto',
contain: 'strict' // 严格限制重排重绘
}}
>
<div style={{ height: items.length * itemHeight, position: 'relative' }}>
{visibleItems.map((item, index) => (
<div
key={item.id}
style={{
position: 'absolute',
top: `${(visibleRange.start + index) * itemHeight}px`,
width: '100%',
height: `${itemHeight}px`,
willChange: 'transform', // 提升为合成层
contain: 'paint' // 限制重绘范围
}}
>
<ItemComponent item={item} />
</div>
))}
</div>
</div>
);
};

3. CSS与动画优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 使用合成属性避免重排
const AnimatedCard = ({ isVisible, position }) => {
const animatedStyle = useMemo(() => ({
transform: `translate3d(${position.x}px, ${position.y}px, 0)`,
opacity: isVisible ? 1 : 0,
transition: 'transform 0.3s ease, opacity 0.3s ease',
willChange: 'transform, opacity', // 提示浏览器优化
contain: 'paint' // 限制重绘范围
}), [isVisible, position]);

return <div style={animatedStyle}>Card Content</div>;
};

// 使用React Portal避免父组件重排影响
const Modal = ({ children, isOpen }) => {
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);

if (!mounted) return null;

return createPortal(
<div
className="modal-overlay"
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: isOpen ? 'flex' : 'none',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000,
willChange: 'opacity' // 合成层优化
}}
>
<div className="modal-content">
{children}
</div>
</div>,
document.body
);
};

二、Vue中的重排重绘优化

1. 响应式系统优化

(1) computed属性缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<template>
<div class="data-container">
<!-- 使用v-once处理静态内容 -->
<header v-once class="static-header">
<h1>数据看板</h1>
<p>最后更新: {{ formattedUpdateTime }}</p>
</header>

<!-- 使用v-show替代v-if用于频繁切换 -->
<div v-show="isLoading" class="loading-skeleton">
<div v-for="n in skeletonCount" :key="n" class="skeleton-item"></div>
</div>

<!-- 条件渲染,减少不必要的DOM操作 -->
<div v-if="!isLoading && items.length">
<div
v-for="item in filteredItems"
:key="item.id"
class="data-item"
:style="{ contain: 'paint' }"
>
<item-display :item="item" />
</div>
</div>
</div>
</template>

<script setup>
import { ref, computed, watch, onMounted } from 'vue';

const rawData = ref([]);
const filters = ref({ search: '', category: '' });
const isLoading = ref(true);

// computed自动缓存,仅在依赖变化时重新计算
const filteredItems = computed(() => {
console.time('Filter computation');
const result = rawData.value.filter(item => {
const matchesSearch = !filters.value.search ||
item.name.toLowerCase().includes(filters.value.search.toLowerCase());
const matchesCategory = !filters.value.category ||
item.category === filters.value.category;

return matchesSearch && matchesCategory;
});
console.timeEnd('Filter computation');
return result;
});

// 使用watch精确控制副作用
watch(
[() => rawData.value.length, () => filters.value],
([newLength], [oldLength]) => {
if (newLength !== oldLength) {
isLoading.value = false;
}
},
{ deep: true }
);
</script>

(2) Vue 3响应式优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<script setup>
import { ref, shallowRef, reactive, readonly, computed } from 'vue';

// 使用shallowRef减少深层响应式开销
const largeDataset = shallowRef([]);

// 使用reactive创建选择性响应式对象
const uiState = reactive({
selectedId: null,
sortBy: 'name',
sortOrder: 'asc',
scrollPosition: 0
});

// 只读配置,避免意外修改
const chartConfig = readonly({
type: 'bar',
options: {
animation: false, // 禁用动画减少重绘
responsive: true,
maintainAspectRatio: false
}
});

// 计算属性缓存复杂逻辑
const summaryStats = computed(() => {
const values = largeDataset.value.map(item => item.value);
return {
total: values.reduce((sum, val) => sum + val, 0),
average: values.length ? values.reduce((sum, val) => sum + val, 0) / values.length : 0,
max: Math.max(...values),
min: Math.min(...values)
};
});
</script>

2. 组件级别优化

(1) 缓存组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<div class="dashboard">
<nav-tabs v-model="activeTab" />

<!-- 缓存切换的组件,避免重复渲染 -->
<keep-alive :include="['OverviewTab', 'AnalyticsTab']" :max="3">
<component :is="currentComponent" :key="activeTab" />
</keep-alive>

<!-- 使用v-memo优化列表渲染(Vue 3.2+) -->
<div v-for="item in items" :key="item.id" v-memo="[item.updated, item.selected]">
<expensive-list-item :item="item" />
</div>
</div>
</template>

<script setup>
import { ref, computed } from 'vue';

const activeTab = ref('overview');
const tabs = {
overview: () => import('./OverviewTab.vue'),
analytics: () => import('./AnalyticsTab.vue'),
settings: () => import('./SettingsTab.vue')
};

const currentComponent = computed(() => tabs[activeTab.value]);
</script>

(2) 异步组件与懒加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
import { defineAsyncComponent, Suspense } from 'vue';

// 懒加载复杂组件
const HeavyChart = defineAsyncComponent({
loader: () => import('./HeavyChart.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorComponent,
delay: 200, // 延迟显示加载状态
timeout: 3000 // 超时时间
});

// 使用Suspense处理异步组件加载
const AsyncDashboard = () => (
<Suspense>
{{
default: () => <HeavyChart />,
fallback: () => <LoadingSpinner />
}}
</Suspense>
);
</script>

3. 虚拟滚动实现(Vue 3版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<template>
<div
ref="containerRef"
class="virtual-scroll-container"
@scroll.passive="handleScroll" <!-- .passive修饰符提升滚动性能 -->
:style="{ contain: 'strict' }"
>
<div :style="{ height: totalHeight + 'px', position: 'relative' }">
<div
v-for="(item, index) in visibleItems"
:key="item.id"
class="virtual-item"
:style="getItemStyle(index)"
>
<data-item :item="item" />
</div>
</div>
</div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';

const props = defineProps({
items: Array,
itemHeight: { type: Number, default: 50 },
buffer: { type: Number, default: 2 }
});

const containerRef = ref(null);
const scrollTop = ref(0);

// 计算可见范围
const visibleRange = computed(() => {
if (!containerRef.value) return { start: 0, end: 0 };

const containerHeight = containerRef.value.clientHeight;
const start = Math.floor(scrollTop.value / props.itemHeight);
const end = Math.min(
start + Math.ceil(containerHeight / props.itemHeight) + props.buffer * 2,
props.items.length
);

return { start, end };
});

// 可见项目
const visibleItems = computed(() => {
return props.items.slice(visibleRange.value.start, visibleRange.value.end);
});

// 总高度
const totalHeight = computed(() => props.items.length * props.itemHeight);

// 获取项目样式
const getItemStyle = (index) => {
return {
position: 'absolute',
top: `${(visibleRange.value.start + index) * props.itemHeight}px`,
width: '100%',
height: `${props.itemHeight}px`,
willChange: 'transform', // 提升为合成层
contain: 'paint' // 限制重绘范围
};
};

// 防抖滚动处理
let animationFrameId = null;
const handleScroll = (e) => {
scrollTop.value = e.target.scrollTop;

if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}

animationFrameId = requestAnimationFrame(() => {
// 批量更新逻辑
animationFrameId = null;
});
};

onUnmounted(() => {
if (animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
});
</script>

三、框架通用优化策略

1. CSS优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 通用优化策略 */
.data-intensive-container {
contain: layout paint; /* 限制重排重绘范围 */
will-change: scroll-position; /* 提示滚动优化 */
}

/* 合成层优化 */
.optimized-animation {
transform: translateZ(0); /* 强制提升为合成层 */
/* 或使用 */
will-change: transform, opacity;
}

/* 避免布局抖动 */
.avoid-layout-thrashing {
contain: style; /* 限制样式变化影响 */
}

2. Web Worker集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 通用Worker处理器
class WorkerPool {
constructor(workerScript, size = 4) {
this.workers = [];
this.taskQueue = [];
this.size = size;

for (let i = 0; i < size; i++) {
const worker = new Worker(workerScript);
worker.onmessage = this.handleResponse.bind(this, worker);
this.workers.push({ worker, busy: false });
}
}

handleResponse(worker, event) {
const { taskId, result } = event.data;
const task = this.taskQueue.find(t => t.id === taskId);
if (task) {
task.resolve(result);
worker.busy = false;
this.processQueue();
}
}

process(operation, data) {
return new Promise(resolve => {
const taskId = Date.now() + Math.random().toString(36).substr(2, 9);
this.taskQueue.push({ id: taskId, data, operation, resolve });
this.processQueue();
});
}

processQueue() {
const idleWorker = this.workers.find(w => !w.busy);
const pendingTask = this.taskQueue.find(t => !t.processing);

if (idleWorker && pendingTask) {
pendingTask.processing = true;
idleWorker.busy = true;
idleWorker.worker.postMessage({
taskId: pendingTask.id,
data: pendingTask.data,
operation: pendingTask.operation
});
}
}
}

// 使用示例
const processor = new WorkerPool('/data-processor.js');

// 在React/Vue组件中
const handleLargeData = async (data) => {
setLoading(true);
try {
const result = await processor.process('sort', data);
setProcessedData(result);
} finally {
setLoading(false);
}
};

总结

React和Vue虽然有不同的响应式机制,但在优化重排重绘方面有很多共同策略:

  1. React:利用Fiber架构的协调机制,通过React.memo、useMemo、useCallback等API精确控制渲染
  2. Vue:利用响应式系统的细粒度追踪,通过computed、v-once、keep-alive等优化渲染

两个框架都支持虚拟滚动、Web Workers、CSS优化等通用技术。选择哪种框架主要取决于团队熟悉度和项目需求,优化思路是相通的。


React与Vue中的重排重绘优化策略
https://zjw93615.github.io/2025/12/06/性能优化/重排/
作者
嘉炜
发布于
2025年12月6日
许可协议