0


React渲染流程与更新diff算法

React 的渲染流程从虚拟 DOM 树的生成到真实 DOM 的挂载和更新是一个层层递进的过程。以下是详细的解析:


渲染流程概述

React 的渲染流程可以分为两个阶段:

  1. 初次渲染(Mounting): 将虚拟 DOM 树转换为真实 DOM,并挂载到页面。
  2. 更新渲染(Updating): 比较新旧虚拟 DOM 树(Diff),仅更新需要变更的部分。

这两个阶段的流程如下:


一、初次渲染(Mounting)

  1. 创建组件树并生成虚拟 DOM- 当 React 应用启动时,调用 React.createRoot(container).render(<App />)。- React 递归调用组件树的 render 方法或 function,生成一个完整的虚拟 DOM 树。- 每个组件都会返回一个表示 UI 的虚拟 DOM 节点。示例:function App() { return ( <div> <h1>Hello, React!</h1> <p>Welcome to the world of React.</p> </div> );}虚拟 DOM 树:{ type:'div', props:{ children:[{ type:'h1', props:{ children:'Hello, React!'}},{ type:'p', props:{ children:'Welcome to the world of React.'}}]}}
  2. 调和(Reconciliation)- React 将虚拟 DOM 树与当前页面的真实 DOM 进行比较(此时页面为空)。- 因为页面中没有 DOM,React 将虚拟 DOM 直接转化为真实 DOM。
  3. 生成真实 DOM 并挂载- React 遍历虚拟 DOM 树,使用 document.createElement 创建真实 DOM 节点。- 为每个节点设置属性(如 classNameid 等),并递归插入子节点。- 最终将构建好的 DOM 树挂载到指定的容器中。示例挂载代码:const container = document.getElementById('root');ReactDOM.createRoot(container).render(<App />);

二、更新渲染(Updating)

当组件的

state

props

发生变化时,React 会重新渲染受影响的部分。此过程包括以下步骤:

1. 检测变化
  • 组件的状态(state)或属性(props)更新时,React 会触发组件的更新。
  • 调用组件的 render 方法或 function,生成新的虚拟 DOM 树。

示例:

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
  • 初始虚拟 DOM:{ type:'div', props:{ children:[{ type:'p', props:{ children:0}},{ type:'button', props:{ children:'Increment', onClick:[Function]}}]}}
  • 当点击按钮后,count 变为 1,生成新的虚拟 DOM:{ type:'div', props:{ children:[{ type:'p', props:{ children:1}},{ type:'button', props:{ children:'Increment', onClick:[Function]}}]}}

2. 比较新旧虚拟 DOM(Diff)

React 使用 Diff 算法 比较新旧虚拟 DOM 树,找出变化部分。

关键步骤:

  1. 类型比较:- 如果节点类型(如 divspan)不同,React 直接移除旧节点并插入新节点。- 如果类型相同,比较属性和子节点。
  2. 属性比较:- 比较新旧节点的属性,仅更新有变化的属性。示例:Old:<div id="old"/>New:<div id="new"/>React 仅更新 id"new"
  3. 子节点比较:- React 使用 key 属性优化动态子节点的比较。- 如果没有 key,React 按序逐一比较,可能导致多余的重建。

3. 应用变化(Patch)
  • React 计算出需要的最小 DOM 操作(增、删、改、移)。
  • 批量更新 DOM,减少重绘和重排的次数。

示例:

  • 假设旧 DOM 是:<div><p>0</p><button>Increment</button></div>
  • 新虚拟 DOM 变为:<div><p>1</p><button>Increment</button></div>
  • React 会:1. 更新 <p> 元素的文本内容从 0 改为 1。2. 不会重新创建 button 元素。

三、Fiber 架构

在 React 16+ 中,更新阶段由 Fiber 架构 驱动,以提高性能。

1. Fiber 的作用
  • 将渲染工作分解为多个小任务(可中断的工作单元)。
  • 实现优先级机制,高优先级任务(如用户交互)可打断低优先级任务(如后台渲染)。
2. Fiber 渲染流程
  1. 渲染阶段(Render Phase): 构建新的 Fiber 树,比较新旧 Fiber 树,计算出需要的更新。- 此阶段是纯粹的计算,不会直接操作 DOM。- 可中断,React 会优先处理高优先级任务。
  2. 提交阶段(Commit Phase): 将更新应用到真实 DOM。- 这是不可中断的过程,React 将计算出的差异(Patch)批量提交到 DOM。

总结

React 的渲染流程从虚拟 DOM 到真实 DOM,大致可以分为以下步骤:

  1. 初次渲染:- 生成虚拟 DOM 树。- 调和并挂载真实 DOM。
  2. 更新渲染:- 检测状态或属性变化。- 生成新虚拟 DOM 树。- Diff 算法比较新旧虚拟 DOM,计算最小变更。- 使用 Fiber 提高性能,将更新应用到真实 DOM。

这种分阶段的设计使得 React 能够高效地渲染和更新 UI,同时保持良好的用户体验。

————————————————————————————————————————————————
React 的 Diff 算法 是其核心性能优化技术,用于比较新旧虚拟 DOM 树的差异,并以最小的代价更新真实 DOM。为了保证效率,React 并没有采用传统的暴力对比方法(时间复杂度为 (O(n^3))),而是结合特定的假设对 Diff 过程进行优化,将时间复杂度降低到 (O(n))。

以下是 Diff 算法的详解:


Diff 算法的优化假设

React 的 Diff 算法基于以下三个假设来简化比较过程:

  1. 不同类型的节点产生完全不同的树- 如果两个节点类型(如 divspan)不同,React 会直接销毁旧节点及其子节点,重新创建新节点,而不是逐一比较子节点。
  2. 开发者可通过唯一的 key 标识子节点- 当比较同一层级的子节点时,React 假定它们的顺序可能会改变,因此会利用 key 来快速定位变化节点。
  3. 同级子节点只与同级的其他子节点进行比较- React 不会跨层级比较节点。比如,新旧虚拟 DOM 树中不同层级的节点不会被直接比较。

Diff 算法的过程

Diff 算法分为以下几个步骤:


1. 树的层次比较
  • React 会逐层比较新旧虚拟 DOM 树,而不会跨层级进行。
  • 如果根节点类型不同,直接替换整个子树。

示例:
旧虚拟 DOM:

<div>
  <p>Old</p>
</div>

新虚拟 DOM:

<span>
  <p>New</p>
</span>

结果:

div

被替换为

span

,整个子树重新创建。


2. 同类型节点的属性比较
  • 对于同类型的节点(如两个 div),React 会逐个比较其属性,并更新有变化的部分。

示例:
旧节点:

<div id="old" className="foo"></div>

新节点:

<div id="new" className="foo"></div>

结果:只更新

id

属性。


3. 子节点的比较

子节点的比较是 Diff 算法的核心。React 会根据子节点是否有

key

来采取不同的策略。


**3.1 无
key

的子节点**

  • React 逐一比较新旧子节点的位置: 1. 如果新节点和旧节点的类型相同,递归比较它们的属性和子节点。2. 如果类型不同,移除旧节点并插入新节点。

示例:
旧子节点:

<ul>
  <li>Apple</li>
  <li>Banana</li>
</ul>

新子节点:

<ul>
  <li>Banana</li>
  <li>Apple</li>
</ul>

结果:React 假定第一个节点由

Apple

变为

Banana

,第二个由

Banana

变为

Apple

,导致两个节点被完全重建。


**3.2 有
key

的子节点**

  • key 提供了稳定的标识,React 可以通过 key 快速找到对应节点,避免不必要的重建。

示例:
旧子节点:

<ul>
  <li key="a">Apple</li>
  <li key="b">Banana</li>
</ul>

新子节点:

<ul>
  <li key="b">Banana</li>
  <li key="a">Apple</li>
</ul>

结果:

  1. React 检测到 key="b" 的节点仍存在,只需更新位置。
  2. 同样,key="a" 的节点只更新位置。

这种方式避免了重建节点,仅调整顺序。


4. 插入、删除、移动

在比较过程中,React 会记录以下操作:

  • 插入新节点:当新节点在旧节点中不存在时,React 会创建并插入它。
  • 删除旧节点:当旧节点在新节点中不存在时,React 会删除它。
  • 移动节点:当 key 相同但位置变化时,React 会调整节点位置。

操作流程:

  1. 生成差异的补丁(Patch)。
  2. 最小化 DOM 操作,应用这些补丁。

Diff 算法的实现机制

React 的 Diff 算法是基于以下过程实现的:

  1. 单节点 Diff- 比较节点类型,相同则继续比较属性和子节点。- 不同则替换节点。
  2. 多节点 Diff- 如果有 key,通过 key 映射对子节点进行高效比较。- 如果无 key,按顺序比较,可能导致不必要的重建。
  3. 最小化操作- React 会批量执行必要的 DOM 更新,减少浏览器的重绘和重排次数。

关键优化点

  1. Key 的使用:- 在动态列表中,使用唯一的 key,可显著提高 Diff 效率。- 不推荐使用索引作为 key,因为顺序改变时可能导致不必要的更新。
  2. 分层比较:- React 限制比较在同一层级进行,避免了跨层级的复杂计算。
  3. 批量更新:- React 使用批处理(Batching)技术,将多次状态更新合并为一次操作,降低性能开销。

总结

React 的 Diff 算法是为了高效地更新真实 DOM,遵循以下原则:

  • 逐层比较,避免跨层级复杂度。
  • 利用 key 优化动态子节点的对比。
  • 尽量减少实际的 DOM 操作。

通过这些优化策略,React 能够在性能与灵活性之间取得良好的平衡。


本文转载自: https://blog.csdn.net/m0_55049655/article/details/143924881
版权归原作者 GISer_Jinger 所有, 如有侵权,请联系我们删除。

“React渲染流程与更新diff算法”的评论:

还没有评论