程序员面试宝典

一站式面试准备平台

返回分类
React高级

React 虚拟 DOM 与 Diff 算法

深入理解 React 虚拟 DOM 的工作原理及 Diff 算法的实现

2026-03-26
阅读时间: 10分钟

React 虚拟 DOM 与 Diff 算法

虚拟 DOM 是 React 的核心概念,理解它的工作原理对于写出高性能 React 应用至关重要。

虚拟 DOM 基础

什么是虚拟 DOM

虚拟 DOM(Virtual DOM)是一个 JavaScript 对象树,表示真实 DOM 的结构:

jsx
// JSX
const element = (
    <div className="container">
        <h1>标题</h1>
        <p>内容</p>
    </div>
);

// 虚拟 DOM 结构(简化)
{
    type: 'div',
    props: { className: 'container' },
    children: [
        { type: 'h1', props: {}, children: ['标题'] },
        { type: 'p', props: {}, children: ['内容'] }
    ]
}

为什么需要虚拟 DOM

直接操作 DOM 是昂贵的,虚拟 DOM 通过以下方式优化:

  1. 批量更新:将多次 DOM 操作合并为一次
  2. 差异化比较:只更新变化的部分
  3. 跨平台能力:抽象层支持 React Native 等平台

React 的协调过程

Reconciliation(协调)

当组件状态变化时,React 会执行协调过程:

状态变化 → 重新渲染组件 → 生成新的虚拟DOM树 → Diff算法比较 → 最小化更新真实DOM

React 的渲染流程

javascript
// 1. 组件状态变化
this.setState({ count: this.state.count + 1 });

// 2. React 标记需要重新渲染
// 3. 生成新的虚拟 DOM 树
// 4. 新旧虚拟 DOM 比较(Diff)
// 5. 计算出最小更新操作
// 6. 批量更新真实 DOM

Diff 算法

Diff 算法的三大策略

React Diff 算法基于三个关键策略:

1. Web UI 中 DOM 节点跨层级操作很少

jsx
// 不推荐:跨层级移动
<A>
  <B />
</A>

// 变为
<A>
  <C>
    <B />
  </C>
</A>

// React 会:删除 B,重新创建 B(性能开销大)

2. 两个不同类型的元素产生不同的树

jsx
// 元素类型改变,整个子树重新渲染
<div>...</div>

// 变为
<span>...</span>

// React 会销毁旧的 div 及所有子节点,重新创建

3. 开发者可以通过 key 标识稳定节点

jsx
// 使用 key 帮助 React 识别节点
<ul>
    {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
    ))}
</ul>

对比类型:Tree Diff

同层级节点比较,基于类型判断:

jsx
// 如果节点类型相同,只更新属性
<div className="old">内容</div>
<div className="new">内容</div>
// → 只更新 className

// 如果节点类型不同,替换整个子树
<div>旧内容</div>
<span>新内容</span>
// → 销毁 div 及其所有子节点,创建新的 span

对比类型:Component Diff

组件类型比较规则:

jsx
// 相同类型的组件:
// 1. 先更新组件实例的 props
// 2. 调用 componentWillReceiveProps
// 3. 调用 componentWillUpdate
// 4. 重新执行 render
// 5. 进行 Tree Diff

// 不同类型的组件:
// → 销毁旧组件,创建新组件
// → componentWillUnmount → componentWillMount → componentDidMount

对比类型:Element Diff

列表元素比较,使用 key 优化:

jsx
// 无 key:可能大量重排
[A, B, C] → [C, A, B] → 删除 C,创建 C(错误)

// 有 key:正确识别移动
[A, B, C] → [C, A, B] → C 移动到开头(正确)

实战:性能优化

使用 React.memo 避免不必要的重渲染

jsx
const MemoizedComponent = React.memo(function MyComponent(props) {
    return <div>{props.value}</div>;
});

// 自定义比较函数
const MemoizedWithCustomCompare = React.memo(
    MyComponent,
    (prevProps, nextProps) => {
        return prevProps.value === nextProps.value;
    }
);

使用 useMemo 和 useCallback

jsx
function ExpensiveComponent({ a, b }) {
    // 只在 a 或 b 变化时重新计算
    const result = useMemo(() => {
        return computeExpensiveValue(a, b);
    }, [a, b]);
    
    return <div>{result}</div>;
}

function Parent() {
    const [count, setCount] = useState(0);
    
    // 只在 callback 变化时返回新的函数引用
    const handleClick = useCallback(() => {
        console.log('clicked');
    }, []);
    
    return <ExpensiveComponent onClick={handleClick} />;
}

列表使用稳定的 key

jsx
// ❌ 错误:使用索引作为 key
{todos.map((todo, index) => (
    <TodoItem key={index} todo={todo} />
))}

// ✅ 正确:使用唯一 ID
{todos.map(todo => (
    <TodoItem key={todo.id} todo={todo} />
))}

// ⚠️ 可接受但不推荐:无重复数据的简单列表
{todos.map(todo => (
    <TodoItem key={todo.text} todo={todo} />
))}

避免内联对象和函数

jsx
// ❌ 错误:每次渲染创建新对象
render() {
    return <Child style={{ color: 'red' }} />;
}

// ✅ 正确:将样式提取为常量
const childStyle = { color: 'red' };
render() {
    return <Child style={childStyle} />;
}

// ✅ 正确:使用 useMemo
render() {
    const style = useMemo(() => ({ color: 'red' }), []);
    return <Child style={style} />;
}

常见面试问题

Q1: 虚拟 DOM 的优缺点?

优点:

  • 减少直接 DOM 操作,提高性能
  • 批量更新,减少重排重绘
  • 跨平台(React Native、Catalyst)
  • 组件化开发体验好

缺点:

  • 首次渲染可能比直接 DOM 慢
  • 内存占用增加
  • 复杂应用中 Diff 算法本身也有开销

Q2: React 为什么要求 key 要稳定?

key 帮助 React 识别哪些元素改变了、添加了或删除了。如果 key 不稳定,React 无法正确跟踪元素:

jsx
// ❌ 问题:key 是随机生成的,每次都不同
{todos.map(todo => (
    <TodoItem key={Math.random()} todo={todo} />
))}

// 结果:React 认为所有元素都是新的,执行销毁+重建

Q3: React Fiber 是什么?

React Fiber 是 React 16 引入的新的协调引擎:

  • 可中断渲染:将渲染工作拆分成小单元,可以中断和恢复
  • 优先级调度:高优先级更新(如用户输入)可以打断低优先级更新
  • 并发模式基础:支持 Suspense、Concurrent Rendering

Q4: shouldComponentUpdate 的作用?

jsx
class Counter extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        // 只有当 count 变化时才重新渲染
        return nextProps.count !== this.props.count;
    }
    
    render() {
        return <div>{this.props.count}</div>;
    }
}

// 推荐使用 React.PureComponent 或 React.memo
class Counter extends PureComponent {
    render() {
        return <div>{this.props.count}</div>;
    }
}

总结

虚拟 DOM 和 Diff 算法是 React 高性能的核心:

  1. Tree Diff:同层级比较,基于类型判断
  2. Component Diff:相同类型组件更新 props,不同类型销毁重建
  3. Element Diff:列表元素使用 key 识别移动

性能优化原则:

  • 保持稳定的 key
  • 避免不必要的重渲染
  • 使用 React.memo、useMemo、useCallback

相关标签