睡前小故事——Diff

React Diff

什么是Diff?

2021年的某个夏天,太阳依旧很耀眼,路边的知了争相嘶吼,道不出与炎日的情愫。我穿着心爱的人字拖,去xx科技公司应聘。面试官说:前一个面试者很有趣让我康康你和他比有没有不一样的东西。我和面试官会心一笑,说到:“好家伙,我来给你讲个故事吧。”

2020年的某个夏天,太阳依旧很耀眼,路边的知了争相嘶吼,道不出与炎日的情愫。我还是上家公司的面试官。老板说要开发新功能,人手不够了,需要招聘一批前端工程师。我说老板你不讲武德,现在是5月份,我去哪里给你招一批前端工程师。老板说:“对不起Lew老师,我不懂规矩,都是乱来的。你能干就干,不能干就滚蛋。”他可不是乱来的啊,JD、福利,训练有素,看来是有备而来。我劝这位老板耗子尾汁,好好反思。

经过一段时间的努力,招到了两名初级前端工程师

<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>  
<div>
复制代码

但是老板不是很满意,说后面有新来的就看情况把他们换掉

Diff分类

单节点Diff

1、今天来了一个人说要应聘初级前端工程师,我问他叫什么名字,他说他叫大黄。
老板说都叫大黄都是,那就用之前的大黄吧,把小白换了。

key===dahuang type===span

<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后-->  
<div key='Lew'>
  <span key='dahuang'></span>
<div>
复制代码

2、今天来了一个人说要应聘初级前端工程师,我问他叫什么名字,他说他叫小白。
老板说都叫小白是吧,先把大黄换了,往后面看,有个叫小白的啊,那这个不用换了。

key===xiaobai type===span

<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后-->  
<div key='Lew'>
  <span key='xiaobai'></span>
<div>
复制代码

3、今天来了一条狗说要应聘初级前端工程师,它说它也叫大黄。老板嗨了,把大黄和小白全换了。

key===dahuang type!==span

<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后-->  
<div key='Lew'>
  <p key='dahuang'></p>
<div>
复制代码

4、今天来了一条狗说要应聘初级前端工程师,它说它也叫小白。老板嗨了,先把大黄换了,往后面看,这个小白是个呐,那也换了。

key===xiaobai type!==span

<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后-->  
<div key='Lew'>
  <p key='xiaobai'></p>
<div>
复制代码

5、今天来了一个人说要应聘初级前端工程师,它说它没名字。老板说不能没名字,给你起一个,你叫就叫null吧。这没一个和你叫一样的,都换了吧。

key===null type===span

<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后-->  
<div key='Lew'>
  <span></span>
<div>
复制代码

多节点Diff

总结一下多节点Diff的场景吧:

  • 节点更新
<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后(更新属性)-->  
<div key='Lew'>
  <span key='dahuang' className='dahuang'></span>
  <span key='xiaobai' className='dahuang'></span>
<div>
<!--更新后(更新类型)-->  
<div key='Lew'>
  <span key='dahuang'></span>
  <p key='xiaobai'></p>
<div>
复制代码
  • 节点新增或减少
<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后(新增)-->  
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
  <span key='xiaohei'></span>
<div>
<!--更新后(减少)-->  
<div key='Lew'>
  <span key='dahuang'></span>
<div>
复制代码
  • 节点位置变化
<!--更新前-->
<div key='Lew'>
  <span key='dahuang'></span>
  <span key='xiaobai'></span>
<div>
<!--更新后-->  
<div key='Lew'>
  <span key='xiaohei'></span>
  <span key='dahuang'></span>
<div>
复制代码

遍历一次来对比结果:

  1. 新节点和旧节点同时遍历完:一次遍历解决问题,没有中断操作,只需要更新组件内部属性
  2. 新节点遍历完,旧节点没有遍历完:新节点中断操作,只需要删除没有遍历到的旧节点
  3. 旧节点遍历完,新节点没有遍历完:旧节点中断操作,只需要新增没有遍历到的新节点
  4. 新节点和旧节点都没遍历完。:这意味着有节点在这次更新中改变了位置

其他的情况相对比较简单,我们来主要演示一下第四种情况

示例不牵扯react源码结构,只是描述换位逻辑

我们用a、b、c、d来表示key为a、b、c、d的4个节点

记住几个关键点:

  • oldFiber的下标 >= lastPlacedIndex,当前节点的位置不变,并且将oldFiber的下标赋值给lastPlacedIndex
  • oldFiber的下标 < lastPlacedIndex, 当前节点挪到oldFiber[lastPlacedIndex]

接下来看一下换位流程:
diff.gif

我的故事讲完了,面试官一脸唏嘘问我:“那你为什么要来面试我们公司呢?”。
我回答:“因为后面来了一条叫Lew的狗,它说要应聘面试官。。。”。

总结

在新的Fiber架构中Reconciler(协调器)与Renderer(渲染器)不再是交替工作,而是由Reconciler通过Diff为Fiber打上增/删/更新的标记,之后由Renderer统一渲染。

本文旨在为大家建立Diff的粗略模型,要想深入学习的话,这里推荐一本免费的React小册React 技术揭秘,本文也是其中Diff篇的读后感。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享