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> 复制代码
遍历一次来对比结果:
- 新节点和旧节点同时遍历完:一次遍历解决问题,没有中断操作,只需要更新组件内部属性。
- 新节点遍历完,旧节点没有遍历完:新节点中断操作,只需要删除没有遍历到的旧节点。
- 旧节点遍历完,新节点没有遍历完:旧节点中断操作,只需要新增没有遍历到的新节点。
- 新节点和旧节点都没遍历完。:这意味着有节点在这次更新中改变了位置。
其他的情况相对比较简单,我们来主要演示一下第四种情况
示例不牵扯react源码结构,只是描述换位逻辑
我们用a、b、c、d来表示key为a、b、c、d的4个节点
记住几个关键点:
- oldFiber的下标 >= lastPlacedIndex,当前节点的位置不变,并且将oldFiber的下标赋值给lastPlacedIndex
- oldFiber的下标 < lastPlacedIndex, 当前节点挪到oldFiber[lastPlacedIndex]
接下来看一下换位流程:
我的故事讲完了,面试官一脸唏嘘问我:“那你为什么要来面试我们公司呢?”。
我回答:“因为后面来了一条叫Lew的狗,它说要应聘面试官。。。”。
总结
在新的Fiber架构中Reconciler(协调器)与Renderer(渲染器)不再是交替工作,而是由Reconciler通过Diff为Fiber打上增/删/更新的标记,之后由Renderer统一渲染。
本文旨在为大家建立Diff的粗略模型,要想深入学习的话,这里推荐一本免费的React小册React 技术揭秘,本文也是其中Diff篇的读后感。