fre 和 react 16 的排位算法

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 ~

相關文章
相關標籤/搜索