Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fre 和 react 16 的 排位算法 #284

Open
yisar opened this issue May 10, 2019 · 3 comments
Open

fre 和 react 16 的 排位算法 #284

yisar opened this issue May 10, 2019 · 3 comments
Labels
react react、react-router、react-redux、webpack、mobx

Comments

@yisar
Copy link
Member

yisar commented May 10, 2019

终于搞定了::><::感动我自己::><::

之前,我说,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}

我们手动给他标号,就不用担心索引的问题了,这里不能用数字作为键值,所以隐式转换成 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

所以在这个用例中,C 插 A,D 也 插 A,更新两次,没毛病

至此,整个排位算法就差不多啦

具体的实现其实还是要考虑很多边界的,其实算法这东西,看实现和没意思

搞懂了本质,自己复现就很简单啦~

最后放上 fre 的 地址:https://github.com/132yse/fre

欢迎 star 与 fork ~

@zxs-1024
Copy link

我被你感动了

@zimv
Copy link
Member

zimv commented May 10, 2019

我也被你感动了

@yisar
Copy link
Member Author

yisar commented May 10, 2019

@zhanghao-zhoushan @zimv 哈哈哈嗝

@acodercc acodercc added the react react、react-router、react-redux、webpack、mobx label May 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
react react、react-router、react-redux、webpack、mobx
Projects
None yet
Development

No branches or pull requests

4 participants