浅谈从源码里面谈react的diff算法

react和vue作为两个目前影响力最大的两套框架,在性能提升方面,都采用了虚拟dom这种理念,在内存中运行,而不会马上改变dom结构。再一个就是使用了diff算法,但是直接diff两棵树会有问题,考虑到一个树上的节点可以复用,以及可以增加或者删除,会导致时间复杂度过大O(n*n*n)

怎么解决?

react和vue都是默认同级比较的,这样就只需要考虑新增以及删除节点的操作复杂度O(n),极大的提高了效率。

当然,vue和react的具体实现还是不同的,vue在diff的时候调用patch函数给dom打补丁,而且当节点元素的type和key相同时,如果className不同的化,vue也会选择直接销毁,而react则会修改属性。但是,还有个不同,那就时列表渲染的时候,vue会采用双指针的方式比较,而react则是设置lastPlacedIndex这个值作为参考,效率上面比vue要稍微差一些?但是为什么呢?

因为react里面的fiber对象时通过单向链表的形式存在的,所以不能用数组里面的优化手段,双指针。

那么就进入正题了,react如何diff?

diff的实质就是对比显示在页面上的current fiber树和JSX对象生成一个workInProgress fiber树的过程。

在源码中diff的入口函数是:reconcilerChildFibers(),这个函数会判断传入的参数的类型,也就是newChild的类型。

1.单节点diff。对应newChild对象为Object,string,number类型的时候。则执行reconcilerSingleElement()。在函数里面会先比较key,再比较type这是个重点,也是react的优化操作。如果key不同,则给child标记删除,若key相同,比较type,如果type不同,则直接删除child及其兄弟节点,因为唯一的key相同但是type不同,其他的就无需比较。type相同,则复用type。

2.多节点diff。对应的newChild类型为Array类型,同级有多个节点。执行reconcilerChildArray()。

这个阶段分两轮遍历,第一轮遍历更新,第二轮遍历增删的情况。(其实这也是react团队在大量的人机交互之后总结出来的,更新的频率比增删的更高)。

第一轮遍历:遍历newChildren,遇到第一个key不同的场景直接跳出遍历,key相同但是type不同则标记DELETION。直到新旧children被遍历完。

第二轮更新:增删直接就根据打好的标记执行相应的操作就好了,重头戏是移动。那react里面针对移动是一个什么算法呢?


// 之前
abcd

// 之后
badc



首先确定移动的参照点:lastPlacedIndex

这个单词的意思可以理解为最新的移动后的元素的位置索引。如上图,在第二轮遍历时,b此时在之前的位置索引为2,因此lastPlacedIndex=1,移动到a,a的位置索引为0<1,则a向后移动,到d,d的位置索引为3>1,改变lastPlacedIndex的值=3,不移动,最后c的索引为2<3,向后移动c。完成移动过程。这里还有个亮点就是:第一轮没有遍历完的oldChild会被映射为Map结构,key是key,value就是对应的fiber。方便查找。

记录自己的学习,如果还能帮到你就更好了。如果有不正确的地方还请批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值