fre 和 react 16 的排位算法

halo,大家好久不贱

之前,我说,react 16 没有 diff,一堆人反驳我,甚至还有人给我截源码……

不过也确实,react 16 的算法,没找到特别合适的文章,看源码又十分要命

所以其实 fre 的排位算法,其实是大部分凭空复现,不过基本的精髓也有了

前提

在阅读本篇文章之前,最好是对传统的 diff 有了解,没了解也没关系,只需要知道,它是俩 vdom 对比,而 children 的 比对是俩数组

然后数组就有索引,遍历完一个索引,就删掉,进行下一轮

但是 react 16 由于 Fiber 特殊的遍历形式,导致,不再有新旧 vdom 了

在 Fiber 的遍历过程,只有一个 fiber 节点在不断更替,我们通常称它为 WIP

然后就拿这个 WIP 去和 新的 vdom 比对

局限

拿一个节点和一个嵌套的对象进行比对,很明显,想遍历没法遍历,想标号没法标号,可以说是没法儿比对的

所以我们想了个办法,首先,将 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 ~

转载于:https://juejin.im/post/5cd62b21e51d45473d10feef

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值