渲染性能优化手段深度解析
系统梳理 React 和 Vue 框架中的渲染性能优化手段,从 shouldComponentUpdate 到 React.memo 再到 Vue 的 v-memo 指令,详解每种方案的原理与适用场景。
一句话概括
渲染性能优化的核心是「减少不必要的渲染」,React 的 shouldComponentUpdate、React.memo 和 Vue 的 v-memo 指令分别从类组件、函数组件和模板三个维度提供了精确控制渲染粒度的能力。
背景与意义
前端框架的核心工作之一是「响应式更新」——当数据变化时自动更新对应的 DOM 节点。但「自动」是一把双刃剑:它带来了开发效率的提升,但也可能导致大量不必要的渲染计算。
以一个典型的企业级表格页面为例:用户点击某一行展开详情,这个操作会触发父组件的状态更新,进而导致整个表格 500 行的所有子组件重新渲染——即使 499 行根本没有发生变化。这种「过度渲染」是性能问题的头号元凶。
渲染性能优化的核心原则可以用一句话概括:只渲染需要更新的部分,跳过不需要更新的部分。
概念与定义
虚拟 DOM(Virtual DOM):框架在内存中维护的一棵轻量级树,用于描述 UI 结构。数据变化时,框架重新生成新的虚拟 DOM 树,并与旧的虚拟 DOM 树进行对比(Diff),找出最小的 DOM 操作集合。
不必要的渲染(Wasted Render):组件的 render 函数执行了、虚拟 DOM 对比做了,但最终的 DOM 没有任何变化。这种「做了等于白做」的渲染就是不必要的渲染。
渲染节流(Render Throttling):通过某种机制跳过不需要的渲染过程,避免浪费计算资源。
最小示例
一个最简单的「不必要的渲染」场景:
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
// ❌ 问题代码:每次父组件更新时,子组件都会重新渲染
function Parent() {
const [count, setCount] = useState(0)
const [text, setText] = useState('hello')
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ExpensiveChild data={count} /> {/* 输入文字时也会重新渲染 */}
</div>
)
}
function ExpensiveChild({ data }) {
console.log('ExpensiveChild re-rendered!')
// 假设这里有大量计算和子元素
return <div>{data}</div>
}
// ✅ 优化后:使用 React.memo
const ExpensiveChild = React.memo(function ExpensiveChild({ data }) {
console.log('ExpensiveChild re-rendered!')
return <div>{data}</div>
})
// 现在只有在 data 变化时才会重新渲染
核心知识点拆解
1. React 类组件:shouldComponentUpdate
在 React 的类组件时代,shouldComponentUpdate(nextProps, nextState) 是最核心的渲染优化手段。它在 render 之前被调用,返回 true 表示需要渲染,返回 false 则跳过。
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
// shouldComponentUpdate 深度使用示例
import { Component } from 'react'
class DataGridRow extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 方案一:浅比较 props
const prevKeys = Object.keys(this.props)
const nextKeys = Object.keys(nextProps)
if (prevKeys.length !== nextKeys.length) return true
for (const key of prevKeys) {
if (this.props[key] !== nextProps[key]) {
// 如果 props 变化了,进一步判断具体变化
if (key === 'data') {
// data 是对象,需要深度比较
if (this.props.data.id !== nextProps.data.id) return true
if (this.props.data.version !== nextProps.data.version) return true
// 其他字段变化不需要渲染?
if (this.props.data.lastModified !== nextProps.data.lastModified) return false
}
if (key === 'style') {
return this.props.data.isSelected !== nextProps.data.isSelected
}
return true
}
}
// 方案二:自定义深度比较
if (nextState.isEditing !== this.state.isEditing) return true
if (nextState.expandedRow !== this.state.expandedRow) return true
return false // 跳过渲染
}
render() {
const { data, onSelect, onExpand } = this.props
console.log('Row rendered:', data.id)
return (
<tr
className={`data-row ${data.isSelected ? 'selected' : ''}`}
onClick={() => onSelect(data.id)}
>
<td>{data.id}</td>
<td>{data.name}</td>
<td>{data.status}</td>
<td>
<button onClick={() => onExpand(data.id)}>展开</button>
</td>
</tr>
)
}
}
shouldComponentUpdate 的注意事项:
- 函数体内不要做昂贵的计算(否则优化效果被抵消)
- 默认返回
true,即「无条件重新渲染」 - 可以结合
PureComponent自动进行浅比较,但要警惕「prop 突变」问题
React.PureComponent 是在类组件中使用更简洁的方式,它自动实现了 shouldComponentUpdate 的浅比较:
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
// PureComponent 自动进行 props/state 的浅比较
import { PureComponent } from 'react'
class TodoItem extends PureComponent {
render() {
return (
<li className={this.props.completed ? 'done' : ''}>
{this.props.text}
</li>
)
}
}
// ⚠️ 注意事项:PureComponent 的浅比较对嵌套对象无效
const obj1 = { a: { b: 1 } }
const obj2 = { a: { b: 1 } }
console.log(obj1 === obj2) // false! 即使内容相同
// ❌ 以下更新方式不会触发重新渲染
const items = this.state.items
items.push(newItem)
this.setState({ items }) // items 引用没变,PureComponent 不会重新渲染
// ✅ 应该使用不可变数据
this.setState({ items: [...items, newItem] })
2. React 函数组件:React.memo
函数组件没有生命周期方法,React 16.6 引入了 React.memo 来提供类似 PureComponent 的能力。
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
// React.memo 基础用法
import { memo } from 'react'
const UserCard = memo(function UserCard({ user, onFollow }) {
console.log('UserCard rendered:', user.id)
return (
<div className="user-card">
<img src={user.avatar} alt={user.name} />
<h3>{user.name}</h3>
<p>{user.bio}</p>
<button onClick={() => onFollow(user.id)}>
{user.isFollowed ? '已关注' : '关注'}
</button>
</div>
)
})
// React.memo 自定义比较函数
const DashboardWidget = memo(
function DashboardWidget({ data, config }) {
// 复杂的仪表盘面板
return (
<div className="widget">
<WidgetHeader title={config.title} />
<WidgetChart data={data.chartData} type={config.chartType} />
<WidgetFooter stats={data.stats} />
</div>
)
},
// 第二个参数:自定义比较函数
(prevProps, nextProps) => {
// 只有 chartData 变化时才重新渲染
if (prevProps.data.chartData !== nextProps.data.chartData) return false
// 只有 stats 变化时才重新渲染
if (prevProps.data.stats !== nextProps.data.stats) return false
// 其他字段变化忽略
return true
}
)
React.memo 的陷阱——匿名函数和对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ❌ 反例:内联函数导致 memo 失效
function Parent() {
// 每次 Parent 渲染时,handleClick 都是新函数
return (
<MemoChild onClick={() => handleClick()} />
)
}
// MemoChild 永远无法跳过渲染,因为 onClick 引用每次都变
// ✅ 正解:使用 useCallback 稳定引用
function Parent() {
const handleClick = useCallback(() => {
// do something
}, []) // 依赖不变,handleClick 引用稳定
return <MemoChild onClick={handleClick} />
}
// ❌ 反例:内联对象
<MemoChild style={{ color: 'red' }} /> // 每次创建新对象
// ✅ 正解:提取常量
const RED_STYLE = { color: 'red' }
<MemoChild style={RED_STYLE} />
3. React Hooks 记忆化:useMemo 与 useCallback
React.memo 防止的是「组件的重新渲染」,而 useMemo 和 useCallback 防止的是「昂贵计算的重复执行」和「不稳定的函数引用」。
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
import { useMemo, useCallback } from 'react'
function Dashboard({ rawData, filters }) {
// useMemo:缓存计算结果,只有依赖变化时才重新计算
const processedData = useMemo(() => {
console.log('Processing data...')
return rawData
.filter((item) => item.status === filters.status)
.map((item) => ({
...item,
score: calculateScore(item.metrics),
rank: computeRank(item),
}))
.sort((a, b) => b.score - a.score)
}, [rawData, filters])
// useCallback:缓存函数引用
const handleItemClick = useCallback((id) => {
dispatch({ type: 'SELECT_ITEM', payload: id })
}, [dispatch])
// 计算完成后的汇总统计
const summary = useMemo(() => ({
totalItems: processedData.length,
averageScore: processedData.reduce((s, i) => s + i.score, 0) / processedData.length,
topPerformer: processedData[0]?.name,
}), [processedData])
return (
<div>
<SummaryPanel summary={summary} />
<DataList data={processedData} onItemClick={handleItemClick} />
</div>
)
}
useMemo 的最佳实践:
- 适用于计算复杂度 O(n log n) 以上的场景
- 简单的拼接或取反不需要 useMemo
- 配合 React Profiler 评估实际收益,不要盲目使用
4. Vue 响应式系统的优化
Vue 的响应式系统基于代理(Proxy / defineProperty),默认只有「用到的数据」变化时才触发渲染更新。但跨组件更新、深层嵌套对象的「死胡同」依然存在。
Vue 2: computed 和 watch 的精细化控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Vue 2 中的渲染优化
export default {
computed: {
// computed 有缓存,只有依赖变化时重新计算
filteredItems() {
return this.items.filter((i) => i.type === this.currentType)
},
// computed 可以返回布尔值控制渲染
shouldShowAdvancedPanel() {
return this.user.role === 'admin' && this.pageSettings.showAdvanced
},
},
watch: {
// 精确监听某个嵌套字段的变化
'user.preferences.theme': function(newVal) {
this.updateTheme(newVal)
// 而不触发整个组件的重新渲染
}
}
}
Vue 3: v-memo 指令
v-memo 是 Vue 3.2 引入的指令,用于精确控制模板片段的重新渲染。它接受一个依赖数组,只有依赖变化时,该 DOM 片段才会更新。
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
<template>
<div class="large-list">
<div
v-for="item in items"
:key="item.id"
v-memo="[item.id, item.updatedAt, item.isSelected]"
class="list-item"
:class="{ selected: item.isSelected }"
>
<ItemAvatar :src="item.avatar" />
<span class="item-name">{{ item.name }}</span>
<span class="item-bio">{{ item.bio }}</span>
<span class="item-stats">{{ item.followers }} 关注者</span>
<button @click="follow(item.id)">{{ item.followText }}</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([])
// items 数组中每个对象可能有几十个字段
// 但只有 id、updatedAt、isSelected 变化时才需要重新渲染该项
// 其他字段(如 bio、followers)变化时,v-memo 会阻止该项的 DOM 更新
</script>
v-memo 的底层原理:Vue 在 patch 阶段检查 vnode 的 dynamicChildren 数组。如果 v-memo 的依赖数组与上次渲染时的值完全相同(=== 比较),则跳过整个 patch 过程,包括该节点及其所有子节点。
1
2
3
4
5
6
7
8
9
10
// Vue 3 源码简化:v-memo 的 patch 逻辑
function patchElement(n1, n2, ...) {
if (n2.dynamicChildren) {
// 只有 dynamicChildren 中的节点需要 diff
patchBlockChildren(n1.dynamicChildren, n2.dynamicChildren, ...)
} else {
// 完整 diff 所有子节点
patchChildren(n1, n2, ...)
}
}
5. React 的 bailout 机制
React 18+ 的自动批处理(Automatic Batching)和 Concurrent 模式下,渲染优化逻辑变得更加精细:
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
// React 18 的 bailout:当子组件 props 不变时跳过
function Parent() {
const [count, setCount] = useState(0)
const [value, setValue] = useState('')
// 以下两种情况,子组件能否跳过渲染?
return (
<div>
<input value={value} onChange={(e) => setValue(e.target.value)} />
<button onClick={() => setCount((c) => c + 1)}>+1</button>
{/* 情况 1:props 引用不变 */}
<StaticChild /> {/* 这个组件没有 props,可以跳过 */}
{/* 情况 2:传递的函数引用稳定 */}
<MemoizedChild onChange={handleChange} /> {/* 需要 useCallback 稳定引用 */}
</div>
)
}
// ⚠️ React.memo 默认浅比较,但对于 children 的处理需要注意
const Layout = memo(({ children, sidebar }) => {
return (
<div className="layout">
<aside>{sidebar}</aside>
<main>{children}</main>
</div>
)
})
// ❌ 以下方式会导致 Layout 重新渲染
function Page() {
return (
<Layout sidebar={<Sidebar />}>
<Content />
</Layout>
)
}
// 因为 JSX 是 React.createElement 的语法糖,每次执行都会创建新对象
// ✅ 提取为稳定引用
function Page() {
const sidebar = useMemo(() => <Sidebar />, [])
const content = useMemo(() => <Content />, [])
return <Layout sidebar={sidebar}>{content}</Layout>
}
实战案例:综合性能优化实践
以下是一个实际的企业级后台管理系统中的渲染优化综合案例:
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
88
89
90
91
92
93
94
// OptimizedDashboard.jsx - 综合渲染优化
import { memo, useCallback, useMemo, useState, useRef } from 'react'
// 1. 列表项:用 React.memo 包裹
const DataRow = memo(function DataRow({ item, isSelected, onSelect, onToggle }) {
return (
<div
className={`data-row ${isSelected ? 'selected' : ''}`}
onClick={() => onSelect(item.id)}
>
<input
type="checkbox"
checked={isSelected}
onChange={() => onSelect(item.id)}
/>
<div className="row-main">
<span className="row-title">{item.title}</span>
<span className="row-status">{item.status}</span>
</div>
<div className="row-actions">
<button onClick={(e) => { e.stopPropagation(); onToggle(item.id) }}>
{item.expanded ? '收起' : '展开'}
</button>
</div>
</div>
)
})
// 2. 状态管理优化:将频繁变化的状态分离
function Dashboard() {
const [selectedIds, setSelectedIds] = useState(new Set())
const [expandedIds, setExpandedIds] = useState(new Set())
const [filters, setFilters] = useState({})
const [items] = useState(generateLargeDataset(2000))
// 分离过滤器状态,避免每次打字时触发列表更新
const [filterText, setFilterText] = useState('')
const [debouncedFilter, setDebouncedFilter] = useState('')
// 使用防抖延迟过滤
useEffect(() => {
const timer = setTimeout(() => setDebouncedFilter(filterText), 300)
return () => clearTimeout(timer)
}, [filterText])
// 3. 筛选逻辑用 useMemo 缓存
const filteredItems = useMemo(() => {
if (!debouncedFilter) return items
return items.filter((item) =>
item.title.toLowerCase().includes(debouncedFilter.toLowerCase())
)
}, [items, debouncedFilter])
// 4. 回调函数用 useCallback 稳定引用
const handleSelect = useCallback((id) => {
setSelectedIds((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}, [])
const handleToggle = useCallback((id) => {
setExpandedIds((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}, [])
// 5. 独立的搜索框组件
return (
<div className="dashboard">
<FilterBar
value={filterText}
onChange={setFilterText}
/>
<div className="data-list-container">
{filteredItems.map((item) => (
<DataRow
key={item.id}
item={item}
isSelected={selectedIds.has(item.id)}
onSelect={handleSelect}
onToggle={handleToggle}
/>
))}
</div>
<SelectionSummary count={selectedIds.size} />
</div>
)
}
底层原理(含源码分析)
1. React Fiber 架构中的渲染调度
React 的渲染优化依赖于 Fiber 架构的调度能力。Fiber 节点包含了判断是否需要更新的关键信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// React Fiber 节点简化结构
{
tag: 5, // 节点类型(HostComponent、FunctionComponent 等)
key: null,
type: 'div',
stateNode: div, // 真实 DOM 节点
// 判断是否需要更新的核心字段
memoizedProps: {}, // 上次渲染的 props
pendingProps: {}, // 本次更新的 props
memoizedState: {}, // 上次渲染的 state
// 更新队列
updateQueue: null,
// 子节点
child: null,
sibling: null,
// 标记是否需要更新
flags: 0,
// 子节点是否需要更新
subtreeFlags: 0,
}
bailout 的判断流程:
1
2
3
1. 检查该 Fiber 是否有 updateQueue(状态更新)
2. 检查 props 是否变化(使用 Object.is 比较)
3. 如果都没有变化,设置 bailout 标记,跳过整个子树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// React 源码中的 bailout 逻辑简化
function beginWork(current, workInProgress, renderLanes) {
if (current !== null) {
// 检查是否有状态更新
const hasUpdate = current.updateQueue !== null
// 检查 props 是否变化
const propsUnchanged = shallowEqual(current.memoizedProps, workInProgress.pendingProps)
if (!hasUpdate && propsUnchanged) {
// bailout:跳过整个子树
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
}
}
// 需要更新,进入正常的 render 流程
switch (workInProgress.tag) {
case FunctionComponent: return updateFunctionComponent(current, workInProgress)
case MemoComponent: return updateMemoComponent(current, workInProgress)
// ...
}
}
2. React.memo 的源码实现
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
// React.memo 的简化实现
function memo(type, compare) {
return {
$$typeof: REACT_MEMO_TYPE,
type, // 原始组件
compare: compare !== undefined ? compare : shallowEqual, // 默认浅比较
}
}
// 在 beginWork 中处理 MemoComponent
function updateMemoComponent(current, workInProgress, renderLanes) {
const prevProps = current.memoizedProps
const nextProps = workInProgress.pendingProps
const compare = workInProgress.type.compare
// 调用自定义比较函数(或默认浅比较)
if (compare(prevProps, nextProps)) {
// props 没变,bailout
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes)
}
// props 变了,重新渲染
const innerType = workInProgress.type.type
return updateFunctionComponent(current, workInProgress, renderLanes, innerType)
}
3. Vue 3 v-memo 源码分析
Vue 3 的 v-memo 在编译阶段会被解析为 VNode 的 dynamicProps 属性:
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
// 编译阶段:v-memo 的转换
// <div v-for="item in items" :key="item.id" v-memo="[item.id, item.updatedAt]">
// 编译后:
render() {
return openBlock(true)
items.value.forEach((item) => {
createBlock(
'div',
{ key: item.id },
// ...children
// 注意:这里不会直接传入 v-memo
)
})
}
// 运行时:patchBlockChildren 中的 memo 检查
function patchBlockChildren(oldChildren, newChildren) {
for (let i = 0; i < newChildren.length; i++) {
const oldVNode = oldChildren[i]
const newVNode = newChildren[i]
// 检查是否有 v-memo
if (newVNode.type === Fragment && newVNode.dynamicProps?.includes('memo')) {
// 比较 memo 依赖数组
if (hasMemoChanged(oldVNode.memo, newVNode.memo)) {
patchVNode(oldVNode, newVNode)
}
} else {
patchVNode(oldVNode, newVNode)
}
}
}
v-memo 的高效之处在于它跳过了整个子树的 patch,包括子组件的 render、diff 和 DOM 操作。这在处理 v-for 列表中的复杂组件时效果尤为显著。
高频面试题解析
Q1: React.memo 和 shouldComponentUpdate 的区别是什么?
考察点:对 React 不同组件形态的理解。
答案核心:
- 使用形态:
shouldComponentUpdate用于类组件(class component),React.memo用于函数组件(function component) - 调用时机:
shouldComponentUpdate在 render 之前调用,可以阻断 render → diff → commit 全过程;React.memo本质上是一个高阶组件,在 Fiber 的 beginWork 阶段决定是否跳过 - 返回值:
shouldComponentUpdate返回true/false(是否需要更新),React.memo的比较函数返回true/false(props 是否相等),逻辑正好相反 - 灵活性:
shouldComponentUpdate可以同时检查 props 和 state,React.memo只能比较 props
Q2: 为什么 Vue 默认不需要手动优化?什么情况下需要 v-memo?
考察点:Vue 响应式系统和 React 的差异理解。
答案核心: Vue 的响应式系统基于 Proxy,组件渲染时访问的数据会被「收集依赖」,只有这些依赖变化时才会重新渲染。这天然避免了「父组件更新导致所有子组件重新渲染」的问题。
需要 v-memo 的场景:
- v-for 大列表中只有少数数据字段变化:Vue 的响应式系统会通知每个列表项,但如果列表项包含静态子组件,使用 v-memo 可以跳过不必要的更新
- 与外部状态管理(如 Pinia/Vuex)配合:状态模块中的某个字段变化会通知所有引用该模块的组件,v-memo 可以精细化控制
- 计算密集型列表:列表项渲染涉及复杂计算时,v-memo 可以跳过大部分更新
Q3: useMemo 和 React.memo 有什么区别?它们可以互相替代吗?
考察点:对记忆化策略的层次理解。
答案核心: 它们优化的是不同层面的「重复工作」:
- React.memo:阻止整个组件的重新渲染(组件级别的 bailout)
- useMemo:阻止组件内部的某一部分计算或表达式的重复执行(表达式级别的缓存)
如果 useMemo 返回的是一个 React 元素(JSX),它的效果和 React.memo 类似,但场景不同:
1
2
3
4
5
// 场景 1:固定子组件,用 useMemo 缓存 JSX
const header = useMemo(() => <Header />, [])
// 场景 2:子组件依赖 props,用 React.memo
<MemoChild data={data} /> // data 不变时,child 不渲染
不能简单替代:React.memo 是组件级别的持久化优化,useMemo 是函数作用域内的缓存,它们在 React 渲染流水线中的工作位置不同。
总结与扩展
本文系统地对比了 React 和 Vue 两大框架中渲染优化的核心手段。从 shouldComponentUpdate 的精细化判断,到 React.memo 的函数组件适配,再到 Vue 3 v-memo 的模板级精确控制,每种方案都有其独特的应用场景。
核心原则:
- 减少渲染范围:让只依赖特定数据的小组件独立更新
- 稳定引用:使用
useCallback/useMemo保持引用稳定 - 精确依赖:不要传递「变化过于频繁」的数据做 props
- 测量先行:用 React Profiler 或 Vue Devtools 确认优化效果
扩展思考:
React 18 的 Concurrent Mode 引入了新的优化范式——「在渲染过程中暂停和恢复」。这意味着未来的渲染优化不再只是「跳过不需要的渲染」,还包括「控制渲染的优先级」。例如,用户输入框的更新优先级应该高于页面底部广告位的更新。
在 Vue 生态中,Suspense 组件和 Teleport 的出现也在改变渲染优化的策略。未来前端渲染优化的重心将从「减少渲染次数」转向「精细化控制渲染时机和优先级」。