文章

渲染性能优化手段深度解析

系统梳理 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 防止的是「组件的重新渲染」,而 useMemouseCallback 防止的是「昂贵计算的重复执行」和「不稳定的函数引用」。

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 的场景:

  1. v-for 大列表中只有少数数据字段变化:Vue 的响应式系统会通知每个列表项,但如果列表项包含静态子组件,使用 v-memo 可以跳过不必要的更新
  2. 与外部状态管理(如 Pinia/Vuex)配合:状态模块中的某个字段变化会通知所有引用该模块的组件,v-memo 可以精细化控制
  3. 计算密集型列表:列表项渲染涉及复杂计算时,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 的模板级精确控制,每种方案都有其独特的应用场景。

核心原则

  1. 减少渲染范围:让只依赖特定数据的小组件独立更新
  2. 稳定引用:使用 useCallback/useMemo 保持引用稳定
  3. 精确依赖:不要传递「变化过于频繁」的数据做 props
  4. 测量先行:用 React Profiler 或 Vue Devtools 确认优化效果

扩展思考

React 18 的 Concurrent Mode 引入了新的优化范式——「在渲染过程中暂停和恢复」。这意味着未来的渲染优化不再只是「跳过不需要的渲染」,还包括「控制渲染的优先级」。例如,用户输入框的更新优先级应该高于页面底部广告位的更新。

在 Vue 生态中,Suspense 组件和 Teleport 的出现也在改变渲染优化的策略。未来前端渲染优化的重心将从「减少渲染次数」转向「精细化控制渲染时机和优先级」。

本文由作者按照 CC BY 4.0 进行授权