halo,你们很久不贱react
以前,我说,react 16 没有 diff,一堆人反驳我,甚至还有人给我截源码……git
不过也确实,react 16 的算法,没找到特别合适的文章,看源码又十分要命github
因此其实 fre 的排位算法,实际上是大部分凭空复现,不过基本的精髓也有了算法
在阅读本篇文章以前,最好是对传统的 diff 有了解,没了解也不要紧,只须要知道,它是俩 vdom 对比,而 children 的 比对是俩数组数组
而后数组就有索引,遍历完一个索引,就删掉,进行下一轮bash
可是 react 16 因为 Fiber 特殊的遍历形式,致使,再也不有新旧 vdom 了dom
在 Fiber 的遍历过程,只有一个 fiber 节点在不断更替,咱们一般称它为 WIPui
而后就拿这个 WIP 去和 新的 vdom 比对spa
拿一个节点和一个嵌套的对象进行比对,很明显,想遍历无法遍历,想标号无法标号,能够说是无法儿比对的code
因此咱们想了个办法,首先,将 vdom 转化成 fiber
这个转换很简单,就是给 vdom 配上 child sibling return,就能够
好比:vdom 长这样:
let vdom = {
type:'div',
children:[
{
type:'h1'
}
]
}
复制代码
它须要被拍平,变成一个 fiber 节点,children 的 第一个为 child,第二个 为 child 的 sibling
let fiber = {
type:'div',
child:{
type:'h1'
},
//...
}
复制代码
而后这样,就变成 两个 fiber节点进行比对了,很不幸,仍是不行,由于即使是拍平成一个对象了,这个对象无法儿遍历,不是咱们想要的
因此我想了个更骚的方法,和 effectLink 同样,对 child 进行收集
每一个 fiber 下面都有一个 childFibers 对象,它包含这个 fiber 下面全部的 child
let fiber = {
type:'div',
child:{
type:'h1'
},
childFibers :{child1,child2,...childs}
//...
}
复制代码
到这里,不少人会问,为何不直接把 vdom 的 children 放上去?为何须要是对象而不是数组?
咱们看一个用例:
A B C -> A C
复制代码
一旦有新增或者删除行为,若是是数组,它的索引会发生变化,好比,C 原来是 [2],由于 B 删掉了,C 变成 [1],B 找不到了
若是说这里有一一对应的遍历行为,也就是传统 diff,能够经过 index++ 或者 delete xxx 的方式控制索引的增减
因此关键点来了,fiber 并无相似的遍历,天然控制不了索引
因此就须要对节点进行标号,对位置进行重排,这就是 react 16 算法的关键所在
没错,【排位】是关键,【比对】是天然发生的
咱们先给节点标号,fre 用了一个巧妙的 hash 去标记
children:{.0:child1,.1:child2,...childs}
复制代码
我手动给他标号,就不用担忧索引的问题了,由于手动写死的标号,不会由于第二轮遍历而发生变化
ps. 这里不能用数字做为键值,因此隐式转换成 hash
而后,针对新增和删除的行为,就这么愉快的搞定了
接下来就要搞更难的,位置的调换,好比这个用例:
A B -> B A
复制代码
没有新增和删除,那么默认是更新,若是更新的话,要更新两次,可是很明显,只须要 B 插入 A 就能够了
咱们要想知道,那些节点须要更换位置,须要对位置进行记录,因此每一个 fiber 上,都有个 index
这里 A 的 index 是 0 变为 1,位置改变,须要插入,而不是更新,B 也是如此
可是不能插两次,因此咱们规定,若是是是被插者,就不能插别人……
到这里,思考一下下面的用例:
A B C D -> B A D C
复制代码
首先,B 插 A,A 被插了,不动,而后 C 插 D ,D 被插了,不动
很好,只须要两次,接着看:
A B C D -> C D A B
复制代码
根据上面的算法,咱们只能知道,C 插 D 和 B 插 A ,并不能知道 C D 和 A B 要不要换
因此咱们再规定,若是说被插者和兄弟是一我的,就不能插,须要换我的
好比这里的 C 原本要插 D,可是发现 D 是本身的兄弟,那就换插 A(firstChild)
因此在这个用例中,C 插 A,D 也 插 A,更新两次,没毛病
至此,整个排位算法就差很少啦
具体的实现其实仍是要考虑不少边界的,其实算法这东西,看实现和没意思
搞懂了本质,本身复现就很简单啦~
react 16 之后的算法,已经再也不是 diff 了,官方全部的源码和注释中,从未出现过 diff 这个单词
可是传统的 diff,如 inferno 等,仍然值得咱们研究,毕竟 Fiber 实际上是个有风险的方案,很难驾驭
以后看看有机会分享一下 inferno 的算法~
最后放上 fre 的 地址:github.com/132yse/fre
欢迎 star 与 fork ~