在上一篇介紹fiber的屬性時,咱們簡單提到了一嘴,fiber的建立過程是一個深度優先的過程。
在之前咱們作深度優先遍歷最多見的一種方法是,,使用樹形結構,先處理父節點,在對每一個子節點進行遞歸操做以達到深度遍歷的目的,這是最多見的方式。
咱們來簡單模擬一下~
react
首先假設咱們有一個以下虛擬dom的結構 :git
let vdomTree = {
id: 1,
children: [
{ id: 2, children: [
{ id: 3, children: null },
{ id: 4, children: null }
]
},
{
id: 5,
children: [ { id: 6, children: null } ]
}
]
}
複製代碼
function beginWork(vdomNode) {
// 這裏對傳進來的vdom節點作一些處理
// 好比建立實例 執行週期 作diff等等
// 最後返回處理後的虛擬dom
return vdomNode
}
複製代碼
而後咱們將這棵虛擬dom樹做爲參數傳給一個用來深度遍歷的函數中github
function deep(parentVdomTree) {
console.log(id)
// 對當前這個父節點自己作一些處理
let vdomAfterTreatment = beginWork(parentVdomTree)
// 而後獲取到當前節點的子節點
let children = vdomAfterTreatment.children
if (!children) return
children.forEach((child) => {
// 對它的每一個子節點進行一樣的操做
// 這樣每一個子節點以及孫子,曾孫子等等節點都會作同樣的處理
deep(child)
})
}
deep(vdomTree)
複製代碼
可是這樣有一個最大的缺點,那就是一旦咱們開始了遞歸遍歷,那麼直到該樹結構中的全部節點都被遍歷完以前,該deep函數的遞歸是不會中止的,也就是說,deep函數的調用棧會不停的入棧和出棧直到全部的節點都遍歷完成。
這樣的話,一旦咱們的應用中存在着成千上萬的嵌套的節點的話,那這個deep的過程可能就會很是的冗長,就十分有可能形成頁面的卡頓,這也是react在16以前一直未解決的問題。
因此若是要是想解決這個問題,咱們必需要設計一種能夠隨時暫停,退出當前deep調用棧的方法。這種方法,就是react16中新的調度算法。
接下來咱們來看一看react16中新的調度算法長成什麼樣
在一個叫作ReactFiberScheduler.js的文件中咱們能夠找到一個叫作workLoop的方法:
算法
函數中省略了一些代碼,我們主要看react是怎麼遍歷的就好~
首先能夠看到把當前的這個workInProgress傳遞進來也就是這個fiber,注意!傳進來的不是next,是next的爹。在我們上面這個例子中,傳進來的就是這個h1的fiber。
3. 以後會進入一個while循環,循環先獲取到它的return以及sibling。return在上一篇文章中提到它表示當前節點的父節點,sibling表示當前節點的兄弟節點。在我們的例子是就是h1的父節點和兄弟節點,分別是MyClass和div的fiber。
下面省略了一堆邏輯代碼以後,會走到一些判斷,這些判斷和外層這個while循環就是一個深度遍歷的過程。
4. 首先是先判斷了sibling(div的fiber),也就是看當前節點是否有兄弟節點,若是有兄弟節點的話,直接就把兄弟節點做爲next返回到performUnitOfWork中,而後再把這個next返回給workLoop進行下一輪的循環。此時返回的是div的fiber。
5. 接下來div的fiber進入workLoop後會從新進入performUnitOfWork。而後再進入beginWork,以後beginWork中發現div有倆子節點,一個h2一個h3,因而循環這倆節點,分別建立出h2和h3對應的fiber,以後h2做爲firstChild被返回給next,本次next不爲null,因此直接被返回到workLoop中,以後h2的fiber做爲下一輪的workInProgress進入performUnitOfWork和beginWork。
6. 此時h2下沒有子節點,因而beginWork返回null,next是null會進入completeUnitOfWork,此時completeUnitOfWork中的參數是h2的fiber。
7. 一樣,找到h2的fiber的兄弟節點和父節點,例子中分別是h3的fiber的div的fiber。而後先判斷是否有兄弟節點,發現有是h3的fiber,把h3的fiber做爲next返回。以後h3的fiber會走同樣的邏輯從新進入performUnitOfWork和beginWork
8. beginWork返回h3的next,發現h3下沒有child,是個null,因而進入completeUnitOfWork。
9. 一樣找到h3的兄弟節點和父節點。發現h3的兄弟節點是個null,父節點是div的fiber。往下走到判斷,第一步看兄弟節點,發現是null,往下走,發現returnFiber不爲null,是div,因而讓當前的這個workInProgress變成div的fiber,continue這個while循環。
10. 至關於從新進入到completeUnitOfWork這個函數,可是此時的workInProgress已經變成了div的fiber了。一樣的,先找到div的兄弟節點和父節點,本例中分別是span和MyClass。
11. 往下走,發現有兄弟節點,因而讓兄弟節點做爲next被返回,此時的next是span的fiber。
12. span的fiber從新進入perfo和beginWork,beginWork返回next,可是span沒有子節點是個null,因而span的fiber進入completeUnitOfWork。
13. completeUnitOfWork中獲取到span 的兄弟節點和父節點,分別是 null 和 MyClass。以後往下走到判斷,發現span沒有兄弟節點,因而用父節點也就是MyClass做爲workInProgress,continue這個while。
14. 此時的workInProgress是MyClass的fiber,找到他的父節點和兄弟節點,發現他的父節點和兄弟節點都是null(其實MyClass的父節點應該是一個叫RootFiber的東西,這個RootFiber我們下一篇說,這裏先暫時理解成null),因而最終這個completeUnitOfWork函數返回了 null 做爲next。
15. 到此爲止,終於,next已經決計爲null了,因而null被返回到workLoop中,workLoop發現nextUnitOfWork是null,因而跳出loop循環,react結束建立fiber的過程。
我們經過一個例子講解了react建立fiber樹的過程,這個過程總結一下就是從根兒開始往下遍歷,一旦某個節點的child是null了,就去遍歷這個節點的兄弟節點,一旦這個節點的兄弟節點也是null了,就去遍歷他父節點的兄弟節點。而後要處理下一個fiber以前,給了react一個機會,一個能夠中斷渲染fiber,能夠將線程交還給瀏覽器的機會。此時被中斷的fiber會記錄在那個nextUnitOfWork全局變量上,這樣當下一次再回來繼續的時候能夠很輕鬆的找到上一次被打斷的地方。
數組
react中不少的地方都用到了這種相似的算法,能夠說這個算法和fiber數據結構是徹底相輔相成的。這就是react中最主要的fiber架構。
瀏覽器
P.S. 以前有同事問我,若是使用廣度遍歷的話,應該也能夠作到隨時暫停遍歷的過程,那爲啥react不用廣度遍歷而非得用深度遍歷。
其實關於這個問題,主要是由於若是咱們的dom節點特別的多的話,那麼咱們就不得不維護一個特別大的數組棧了,而後每次當中斷了遍歷後,下一次再想遍歷的話,還須要再去這個數組中去找上一次的節點。可是咱們若是使用fiber這種數據結構的話,fiber是一種自帶上下文的對象結構,因此能夠很輕鬆的配合深度遍歷找到上一次中斷的地方。這個是我認爲react不採用廣度遍歷而採用深度遍歷的緣由。數據結構
gayhub地址:github.com/y805939188/… 跪求大佬賞星星架構