React的diff算法
是什么
DOM diff 就是对比两棵虚拟 DOM 树的算法。当组件变化时,会render 出一个新的虚拟 DOM,diff 算法对比新旧虚拟 DOM 之后,得到一个 patch,然后 React 用 patch 来更新真实 DOM。
怎么做
首先,对比两棵树的根节点
- 如果根节点的类型改变了,比如 div 变成了 p,那么直接认为整棵树都变了,不再对比子节点。此时直接删除对应的真实 DOM 树,创建新的真实 DOM 树。
- 如果根节点的类型没变,就看看属性变了没有
a. 如果没变,就保留对应的真实节点
b. 如果变了,就只更新该节点的属性,不重新创建节点。
i. 更新 style 时,如果多个 css 属性只有一个改变了,那么 React 只更新改变的。
然后,同时遍历两棵树的子节点,每个节点的对比过程同上,不过存在如下两种情
况。
a1. 情况一
<ul>
<li>A</li>
<li>B</li>
</ul>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
React 依次对比 A-A、B-B、空-C,发现 C 是新增的,最终会创建真实 C节点插入页面。 3. 情况二
<ul>
<li>B</li>
<li>C</li>
</ul>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
React 对比 B-A,会删除 B 文本新建 A 文本;对比 C-B,会删除 C 文本,新建 B文本;(注意,并不是边对比边删除新建,而是把操作汇总到 patch 里再进行DOM 操作。)对比空-C,会新建 C 文本。
你会发现其实只需要创建 A 文本,保留 B 和 C 即可,为什么 React 做不到呢?
因为 React 需要你加 key 才能做到:
<ul>
<li key="b">B</li>
<li key="c">C</li>
</ul>
<ul>
<li key="a">A</li>
<li key="b">B</li>
<li key="c">C</li>
</ul>
React 先对比 key 发现 key 只新增了一个,于是保留 b 和 c,新建 a。
Vue的diff算法(双端交叉对比算法):
参考:
后续总结
React Dom diff和Vue Dom diff的区别:
- React 是从左向右遍历对比,Vue 是双端交叉对比。
- React 需要维护三个变量(有点扯),Vue 则需要维护四个变量。
- Vue 整体效率比 React 更高,举例说明:假设有 N 个子节点,我们只是把最后子节点移到第一个,那么
a. React 需要进行借助 Map 进行 key 搜索找到匹配项,然后复用节点
b. Vue 会发现移动,直接复用节点
React DOM diff 代码查看流程:
- 运行 git clone github.com/facebook/re…
- 运行 cd react; git switch 17.0.2
- 用 VSCode 或 WebStorm 打开 react 目录
- 打开 packages/react-reconciler/src/ReactChildFiber.old.js 第 1274 行查看旧
版代码,或打开 packages/react-reconciler/src/ReactChildFiber.new.js 第
1267 行查看新代码(实际上一样) - 忽略所有警告和报错,因为 React JS 代码中有不是 JS 的代码
- 折叠所有代码
- 根据 React 文档中给出的场景反复在大脑中运行代码
a. 场景0:单个节点,会运行到 reconcileSingleElement。接下来看多个节点的
情况。
b. 场景1:没 key,标签名变了,最终会走到 createFiberFromElement(存疑)
c. 场景2:没 key,标签名没变,但是属性变了,最终走到 updateElement 里的
useFiber
d. 场景3:有 key,key 的顺序没变,最终走到 updateElement
e. 场景4:有 key,key 的顺序变了,updateSlot 返回 null,最终走到
mapRemainingChildren、updateFromMap 和
updateElement(matchedFiber),整个过程较长,效率较低 - 代码查看要点:
a. 声明不看(用到再看)
b. if 先不看(但 if else 要看)
c. 函数调用必看 - 必备快捷键:折叠所有、展开、向前、向后、查看定义