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 ~