react diff算法

diff算法, 是 Virtual DOM 产生的一个概念, 用来计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面,从而提高了页面渲染效率

diff算法出现的背景

调用 React 的 render() 方法,会创建一棵由 React 元素组成的树。在下一次 state 或 props 更新时,相同的 render() 方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来更新 UI。

这个算法问题有一些通用的解决方案,即生成将一棵树转换成另一棵树的最小操作数。 然而,即使在最前沿的算法中,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素的数量。

如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围。这个开销实在是太过高昂。

为了降低算法复杂度,react通过一些限制策略,提出了一套O(n) 的启发式算法。

Diff算法的限制策略

  • 只对同级元素进行Diff。如果一个DOM节点在前后两次更新中跨越了层级,那么React不会尝试复用他。
  • 两个不同类型的元素会产生出不同的树。如果元素由div变为p,React会销毁div及其子孙节点,并新建p及其子孙节点。
  • 开发者可以通过 key属性 来暗示哪些子元素在不同的渲染下能保持稳定(即是否可稳定复用)

Diff是如何实现的

diff算法的入口函数是reconcileChildFibers,在函数内部,会根据newChild类型,是调用不同的处理函数。
newChild类型一般为object、string、number,其他情况有其他函数处理,以上类型都没有命中,会删除该节点。

这里的newChild参数就是本次更新的 JSX 对象(对应ClassComponent的this.render方法返回值,或者FunctionComponent执行的返回值)

入口函数reconcileChildFibers源码:

// 根据newChild类型选择不同diff函数处理
function reconcileChildFibers(returnFiber: Fiber,currentFirstChild: Fiber | null,newChild: any,
): Fiber | null {const isObject = typeof newChild === 'object' && newChild !== null;if (isObject) {// object类型,可能是 REACT_ELEMENT_TYPE 或 REACT_PORTAL_TYPEswitch (newChild.$typeof) {case REACT_ELEMENT_TYPE:// 调用 reconcileSingleElement 处理// ...其他case}}if (typeof newChild === 'string' || typeof newChild === 'number') {// 调用 reconcileSingleTextNode 处理}if (isArray(newChild)) {// 调用 reconcileChildrenArray 处理}// 一些其他情况调用处理函数// 以上都没有命中,删除节点return deleteRemainingChildren(returnFiber, currentFirstChild);
}

不同类型的Diff是如何实现的

从同级的节点数量将Diff分为两类:

  • newChild类型为object、number、string,代表同级只有一个节点
  • newChild类型为Array,同级有多个节点

情况一:同级只有一个节点的Diff

  • React通过先判断该节点的key是否相同,如果key相同再判断节点类型type是否相同,只有二者都相同时,那么这个DOM节点才能复用。

情况二:同级多个节点的Diff

用新集合中的节点去和老集合中的节点进行diff:

  • diff结束后,如果新集合中还有未进行diff的节点,说明该节点是需要新增的,对该节点执行新增逻辑。
// 之前
<ul><li key="0">0<li><li key="1">1<li>
</ul>// 之后情况1 新增节点
<ul><li key="0">0<li><li key="1">1<li><li key="2">2<li>
</ul>
  • diff结束后,如果老集合中还有未进行diff的节点,说明该节点是需要删除的,对该节点执行删除逻辑。
// 之前
<ul><li key="0">0<li><li key="1">1<li>
</ul>
// 之后情况2 删除节点
<ul><li key="0">0<li>
</ul>
  • diff结束后,如果新老集合都还有未进行diff的节点,说明在diff过程中,出现了不可复用的节点,对该节点进行更新。
// 之前<li key="0">0</li><li key="1">1</li><li key="2">2</li>// 之后<li key="0">0</li><li key="1">1</li><div key="2">2</div><li key="3">3</li>
  • diff结束后,如果新老集合中的节点刚好diff完,(1)新旧集合中存在相同节点,并且位置相同,无需移动节点。(2)新旧集合中存在相同节点但位置不同时,需要移动对老集合中的节点进行移动处理。
// 之前
<ul><li key="0">0<li><li key="1">1<li>
</ul>// 之后
<ul><li key="1">1<li><li key="0">0<li>
</ul>

新旧集合中存在相同节点但位置不同时,如何移动节点?


diff过程:
(1) React先从新中取得B,然后判断旧中是否存在相同节点B,当发现存在节点B后,就去判断是否移动B。
B在旧 中的index=1,它的lastIndex=0,不满足 index < lastIndex 的条件,因此 B 不做移动操作。此时,lastIndex=(index,lastIndex)中的较大数=1.

在Diff函数的入口处,定义一个变量let lastIndex = 0;
该变量表示当前最后一个可复用节点,对应的oldFiber在上一次更新中所在的位置索引。我们通过这个变量判断节点是否需要移动。
注意:
lastIndex有点像浮标,或者说是一个map的索引,一开始默认值是0,它会与map中的元素进行比较,比较完后,会改变自己的值的(取index和lastIndex的较大数)。

(2)看着 A,A在旧的index=0,此时的lastIndex=1(因为先前与新的B比较过了),满足index<lastIndex,因此,对A进行移动操作,此时lastIndex=max(index,lastIndex)=1

(3)看着D,D在旧的index=3,此时lastIndex=1, 不满足index<lastIndex, 所以不移动。比较完后,lastIndex=max(index,lastIndex)=3
(4)看着C,C在旧的index=2,此时lastIndex=3, 满足index<lastIndex,所以C进行移动。
由于C已经是最后一个节点,所以diff操作结束。

特殊的case

// 之前
abcd// 之后
dabc

diff移动过程:

结论: abcd —> dabc的移动过程,并非保持abc不动,将d移动到abc的前面。实际上是保持了d不动,而是将abc移动到了d的后面。

  • 比较d, d在老中存在,且index=3,默认的lastIndex=0, 不满足index<lastIndex,所以d不移动,然后将lastIndex进行赋值lastIndex=3
  • 比较a、b、c,这个三个节点在老中的index值(分别为0、1、2),都小于lastIndex=3,所以会依次移动这三个节点到d的后面,完成diff移动过程。

理想情况是只移动D,不移动A,B,C。因此,在开发过程中,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响React的渲染性能。

值得推荐的参考文档:

1、React源码揭秘3 Diff算法详解

2、React之diff算法

React Diff算法详解相关推荐

  1. react循环key值_React源码揭秘(三):Diff算法详解

    编者按:本文作者奇舞团前端开发工程师苏畅. 代码参照React 16.13.1 什么是Diff 在前两篇文章中我们分别介绍了 React 的首屏渲染流程1和组件更新流程2,其中 首屏渲染会渲染一整棵 ...

  2. diff算法_React源码揭秘(三):Diff算法详解

    编者按:本文作者奇舞团前端开发工程师苏畅. 代码参照React 16.13.1 什么是Diff 在前两篇文章中我们分别介绍了 React 的首屏渲染流程1和组件更新流程2,其中 首屏渲染会渲染一整棵 ...

  3. vue 虚拟dom和diff算法详解

    虚拟dom是当前前端最流行的两个框架(vue和react)都用到的一种技术,都说他能帮助vue和react提升渲染性能,提升用户体验.那么今天我们来详细看看虚拟dom到底是个什么鬼 虚拟dom的定义与 ...

  4. 【vue】diff 算法详解

    一.diff算法是什么 diff算法是一种通过同层的树节点进行比较的高效算法 diff算法的目的就是找出新旧不同虚拟DOM之间的差异,使最小化的更新视图,所以 diff 算法本质上就是比较两个js对象 ...

  5. deepdiff函数返回_Linux diff命令详解

    diff(difference)命令常用来比较两个文件中的内容.diff 命令在最简单的情况下,比较两个文件的不同.如果使用"-"代替"文件"参数,则要比较的内 ...

  6. 计算机图形几何算法详解勘误

    一直在看<计算机图形几何算法详解>这本书,但是在用的过程中发现了一些错误,一直以为是自己的错误,后来在网上找到了这本书的勘误信息,不过是英文原版的,但是还是想贴出来,以便查找 07 Jul ...

  7. 【转】Linux diff 命令详解

    diff 命令详解 1.概述 2.diff如何工作,如何理解diff的执行结果 3.Normal模式 4.Context模式 5.Unified模式 6.比较目录 7.参数 -e 将比较的结果保存成一 ...

  8. Matlab人脸检测算法详解

    这是一个Matlab人脸检测算法详解 前言 人脸检测结果 算法详解 源代码解析 所调用函数解析 bwlabel(BW,n) regionprops rectangle 总结 前言 目前主流的人脸检测与 ...

  9. 图论-最短路Dijkstra算法详解超详 有图解

    整体来看dij就是从起点开始扩散致整个图的过程,为什么说他稳定呢,是因为他每次迭代,都能得到至少一个结点的最短路.(不像SPFA,玄学复杂度) 但是他的缺点就是不能处理带负权值的边,和代码量稍稍复杂. ...

最新文章

  1. PLUS | 包含蛋白质特异性的新型预训练方案
  2. 人脸妆容迁移---研究和思考
  3. 设计模式之迭代子模式
  4. repo 的用法和理解
  5. PicGo复制自定义链接
  6. Mysql -uroot -p 登陆不上_mysql服务启动却连接不上的解决方法
  7. android: 记录及回复lisView的位置
  8. html 表格 单击,在HTML表格中单击“空”单元格
  9. .NET设计模式(9):桥接模式(Bridge Pattern)(转)
  10. java返回页面顶部代码_js返回顶部
  11. 什么是PXE及PXE作用
  12. 嵩天python测验_北理 嵩天老师 Python程序设计 测验易错题总结
  13. 可控硅型号怎样识别_可控硅型号分类
  14. python re库,Python中的Re库简要总结
  15. 网赚点击通用教程! - 健康程序员,至尚生活!
  16. unity3d 理解刚体(Rigidbody)和碰撞体(Collider)以及触发器(Is Trigger),边学边更新
  17. android setdata方法,Android intent.setData方法
  18. 程序员的算法趣题Q56: 鬼脚图中的横线(思路2的Python题解)
  19. 六元均匀直线阵的各元间距为_天线原理考试卷B.doc
  20. 狐狸面具的绘画教程及素材

热门文章

  1. GOPATH 和 GOMOD
  2. 2020 54所提前批
  3. 服务器销售要会的知识,原神:新手上路的六大知识点,一文看懂服务器选择,氪金建议...
  4. 安卓高德地图聚合点击事件_滴滴接入第三方平台服务商,试水聚合,又是一场新的运力争夺?...
  5. Fullpage指定某一屏内容不垂直居中
  6. 一个ocr翻译脚本,扫描版pdf伴侣。
  7. 高教版计算机应用基础,2016计算机应用基础(高教版)授课教案:文档的编辑排版.doc...
  8. 三翼鸟获“年度场景新物种”,因为有可期的新未来
  9. 疑似天津联通hei产:记一次被流量劫持薅羊毛
  10. 2008中国手机客户端市场发展状况浅析