DOM-DIFF

在 React17+ 中 DOM-DIFF 就是根据老的 fiber 树和最新的 JSX 对比生成新的 fiber 树的过程

正常树的 diff 算法时间复杂度是 O(n^3),但是这个时间复杂度太高,所以 React 进行了一些优化

React 中的优化规则

  • 只对同级节点进行对比,如果 DOM 节点跨层级移动,则 React 不会复用
  • 不同类型的元素会产出不同的结构,会销毁老的结构,创建新的结构
  • 可以通过 key 标识移动的元素

单节点,新的元素只有一个的情况

1. type 不同

<div>
  // 在调和阶段,需要把这个老节点标记为删除
  <h1 key="null">h1</h1> 
</div>

更新为:

<div>
  // 生成新的 fiber 节点并标记为插入
  <h2 key="null">h2<h2>
</div>

在 commit 阶段,会执行两个操作:

  • div.removeChild(h1)
  • div.appendChild(h2)

2. type 和 key 都不同

<div>
  // h1 标记为删除
  <h1 key="h1">h1</h1>
  <h2 key="h2">h2</h2>
  <h3 key="h3">h3</h2>
</div>

更新为:

<div>
  <h2 key="h2">h2</h2>
</div>

对于这个案例,先拿 h2 和 h1 匹配,发现不能复用,把 h1 标记为删除,匹配到 h2,可以复用,然后把剩下的节点全部删除

  • div.removeChild(h1)
  • div.removeChild(h3)

3. key 相同 type 也相同

<div>
  <h1 key="h1">h1</h1>
</div>

更新为:

<div>
  <h1 key="h1">h1 - new</h1>
</div>

如果对比后发现新老节点一样的,那么会复用老节点,复用老节点的 DOM 元素和 Fiber 对象

再看属性有无变更,如果有变化,则会把此 Fiber 节点标记为更新

h1.innerHTML = 'h1 - new'

4. key 相同但是 type 不同,直接删除所有老节点

<div>
  <h1 key="h1">h1</h1>
  <h2 key="h2">h2</h2>
</div>

更新为:

<div>
  <p key="h1">p</p>
</div>

如果 key 相同,但是 type 不同,则不再进行后续对比了,因为 key 一样代表是同一个元素,如果 type 不一样说明节点已经发生改变,直接把老节点全部删掉,插入新节点即可

key 相同表示这是同一个元素

  • div.removeChild(h1)
  • div.removeChild(h2)
  • div.appendChild(p)

多节点

如果有多个节点,节点有可能会更新,删除,新增,多节点的时候会经过两轮遍历

第一轮遍历主要处理节点的更新,更新包括属性和类型的更新

第二轮遍历主要处理节点的新增、删除和移动

移动时的原则是尽量少量的移动,如果必须有一个要动,地位高的不动,地位低的动

对比,都可复用,只需更新

<ul>
  <li key="A">A</li>
  <li key="B">B</li>
  <li key="C">C</li>
  <li key="D">D</li>
</ul>

更新为:

<ul>
  <li key="A">A - new</li>
  <li key="B">B - new</li>
  <li key="C">C - new</li>
  <li key="D">D - new</li>
</ul>

最后会得到一个操作序列:

  1. 更新 A
  2. 更新 B
  3. 更新 C
  4. 更新 D

key 相同,type 不同

<ul>
  <li key="A">A</li> // Fiber 节点
  <li key="B">B</li>
  <li key="C">C</li>
  <li key="D">D</li>
</ul>

更新为:

<ul>
  <div key="A">A - new</div> // JSX 节点
  <li key="B">B - new</li>
  <li key="C">C - new</li>
  <li key="D">D - new</li>
</ul>

第一步比较第一个节点,发现不能复用,老的标记删除,新的标记插入

后面的都可复用

最后结论是:

删除老的 li A,插入 divA,更新 B C D

DOM-DIFF 是一个对比老的 Fiber 链表和新的 JSX 数组,生成新的 Fiber 链表的过程

老的是 Fiber,新的是 JSX

key 不同退出第一轮循环

因为如果 key 不一样已经发生位置变化了

<ul>
  <li key="A" style={oldStyle}>A</li>
  <li key="B" style={oldStyle}>B</li>
  <li key="C" style={oldStyle}>C</li>
  <li key="D" style={oldStyle}>D</li>
  <li key="E" style={oldStyle}>E</li>
  <li key="F" style={oldStyle}>F</li>
</ul>

更新为:

<ul>
  <li key="A" style={oldStyle}>A - new</li>
  <li key="C" style={oldStyle}>C - new</li>
  <li key="E" style={oldStyle}>E - new</li>
  <li key="B" style={oldStyle}>B - new</li>
  <li key="G" style={oldStyle}>G</li>
</ul>

首先第一轮循环

oldA 和 newA 比较,一样,可以复用,更新 A

oldB 和 newC 比较,key 不一样,跳出第一轮循环

进行第二轮循环

建一个 map 对象 let map = {'B': B, 'C': C, ' 'D': D, 'E': E, 'F': F}(不处理 A,因为 A 在第一轮循环已经处理完了) 表示还没有复用的节点

继续遍历新节点,到 newC 了

newC 节点在 map 找有没有 key 为 C 的 fiber 节点

如果有,并且可以复用(Fiber 和 DOM 可以复用),说明只是位置变了,把 oldC 标标记为更新

  • lastPlacedIndex 表示最大的不需要动的老的节点的索引

  • oldC 的 oldIndex 是 2,oldIndex > lastPlacedIndex,所以 C 不需要动

  • lastPlacedIndex = 2

  • map 中删除 C

再看 newE

  • 在 map 里面找 E,找到了 oldE,oldIndex 是 4,lastPlacedIndex < 4,E 也不动

  • lastPlacedIndex = 4

  • map 中删除 E

到 newB 了,找到 oldB,oldIndex = 1 < lastPlacedIndex,oldB 需要往后挪

newG 不在 map 里面,标记为插入

等 JSX 数组全部遍历完之后,把 map 里面全部 fiber 节点标记为删除,也就是 oldE 和 oldF

在 react 里面,每一个操作都有一个权重(上图中那个表),每种操作都有权重,这些都记录在 effectList 里面

每次 DOM Diff 会生成一个 effectList,在 commitMutationEffects(ReactFiberWorkLoop.old.js) 会使用,根据 effectList 进行操作

另外,那个优先级在 ReactFiberFlags.js 里面可以找到

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,427评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,551评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,747评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,939评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,955评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,737评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,448评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,352评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,834评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,992评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,133评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,815评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,477评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,022评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,147评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,398评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,077评论 2 355

推荐阅读更多精彩内容