深刻理解React的協調算法

前言

React是一個用於構建界面的JavaScript庫。它的核心是跟蹤組件狀態變化並將更新後的狀態更新到屏幕上。在React中,咱們把這個過程稱爲 reconciliation (協調)。經過調用setState方法,React檢查狀態或屬性是否已更改,並在UI層上更新。html

背景介紹

首先看一個簡單的例子:node

class ClickCounter extends React.Component {
    constructor(props) {
        super(props);
        this.state = {count: 0};
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }


    render() {
        return [
            <button key="1" onClick={this.handleClick}>Update counter</button>,
            <span key="2">{this.state.count}</span>
        ]
    }
}

1.gif

這是一個簡單的計數器的例子, 點擊按鈕,組件的狀態就會在處理程序中更新,組件的狀態的更新反過來會引發span元素的內容更新。react

下面是在協調階段,React內部會有各類活動。如下是計數器在協調階段所作的操做:git

  1. 更新state的count屬性
  2. 檢查並比較ClickCounter的子代以及props
  3. 更新span元素

協調期間還會執行其餘活動,例如調用生命週期方法,更新ref。這些活動在Fiber架構中統稱爲"work"(工做)。work的類型取決於React元素的類型。React元素有多種類型,好比類組件,函數組件,Portals,DOM節點等。而React元素類型則是由React.createElement的第一個參數決定。React.createElement函數在render建立元素中調用。github

React.createElement到Fiber

JSX編譯

<button key="1" onClick={this.onClick}>Update counter</button>
<span key="2">{this.state.count}</span>

JSX在通過編譯後,會獲得以下的結果。這是render方法真正返回的結果算法

class ClickCounter {
    ...
    render() {
        return [
            React.createElement(
                'button',
                {
                    key: '1',
                    onClick: this.onClick
                },
                'Update counter'
            ),
            React.createElement(
                'span',
                {
                    key: '2'
                },
                this.state.count
            )
        ]
    }
}

React.createElement函數會返回以下的結果:vim

[
    {
        $$typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: 'Update counter',
            onClick: () => { ... }
        }
    },
    {
        $$typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {
            children: 0
        }
    }
]
  • $$typeof屬性,用於將它們標示爲react元素
  • type,key,props,用於描述元素

而對於組件<ClickCounter>的元素,它沒有props和key:數組

{
    $$typeof: Symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: ClickCounter
}

Fiber節點

在協調期間,render方法返回的React元素會被合併到Fiber節點樹之中。每個React元素都有對應的Fiber節點。與React元素不一樣,Fiber不會在每一次渲染的時候從新建立。Fiber會保存組件的狀態和DOM。架構

前面討論過,根據不一樣的React元素類型,會執行不一樣的活動。例如,對於Class組件會調用生命週期方法以及render方法。而對於DOM節點,它執行DOM mutation。所以,每一個React元素都被轉換爲相應類型的Fiber節點。節點描述了須要完成的"work"。app

能夠將Fiber節點看做一種數據解構,表示一個work單元。,Fiber架構還提供了一種跟蹤、調度、暫停和停止work的方法。

React會在首次將React元素轉換爲Fiber節點時,使用createFiberFromTypeAndProps函數建立Fiber節點。在更新階段會複用Fiber節點,並使用React元素上的數據更新Fiber節點上的屬性。亦或者移動,刪除Fiber節點。

React源碼中的ChildReconciler函數包含了Fiber節點中全部的work

React會爲每個React元素建立一個Fiber節點,咱們會獲得一個Fiber節點樹

2.png

Fiber節點樹是經過鏈表的形式存儲的,每個Fiber都擁有child(第一個子節點的引用),sibling(第一個兄弟節點的引用)和return(父節點的引用),來表示層級關心。更多內容請參考這篇文章React Fiber爲何使用鏈表來設計組件樹

current tree 和 progress tree

在第一次渲染完成後,React會生成一個Fiber樹。該樹映射了應用程序的狀態,這顆樹被稱爲current tree。當應用程序開始更新時,React會構建一個workInProgress tree, workInProgress tree映射了將來的狀態。

全部的"work"都是在workInProgress tree上的Fiber節點上進行的。當React開始遍歷current tree時,它會爲每個現有的Fiber節點建立一個備份(alternate字段),alternate節點構成了workInProgress tree。當全部更新和相關的"work"完成。workInProgress tree會刷新到屏幕上。workInProgress tree此時變爲了current tree

React的核心原則之一是"一致性", 它老是一次性更新DOM, 不會顯示部分結果. workInProgress就是一個用戶不可見的"草稿", React在它上面處理全部組件, 處理完成後將它再刷新到界面上.

在React的源碼中,有不少從workInProgress treecurrent tree中獲取Fiber節點的函數。好比下面這個函數簽名

function updateHostComponent(current, workInProgress, renderExpirationTime) {
    ...
}

workInProgress tree的Fiber節點擁有current tree對應節點的引用。反之亦然。

反作用

咱們能夠將React組件視爲使用state和props計算UI的函數。其餘的活動,好比手動修改DOM,調用生命週期都應該被視做一種反作用。在React的文檔中也說起了這一點

你以前可能已經在 React 組件中執行過數據獲取、訂閱或者手動修改過 DOM。咱們統一把這些操做稱爲「反作用」(side-effects),或者簡稱爲「做用」(effects)。由於它們會影響其餘組件,而且在渲染期間沒法完成。

大多數state和props的更新都會致使反作用。應用effects是一種work類型。所以Fiber節點是一種跟蹤更新和effects的便捷機制,每個Fiber節點都有與之相關聯的effects。它們被編碼在effectTag字段之中

Fiber中的effects定義了處理更新以後須要作的"work"。對於DOM元素,"work"包含了添加,更新,刪除。對於類組件,包括了更新ref,調用componentDidMount和componentDidUpdate生命週期方法。還有其餘effects對應於其餘類型的Fibber。

Effects list

React處理更新很是快。爲了達到更好的性能水平,採用了一些有趣的技術。其中之一就是將具備effects的Fiber節點,構建爲線性列表,以方便快速迭代。迭代線性列表要比迭代樹快的多,由於不須要迭代沒有side-effects的節點。

effects list的目的是是標記出具備DOM更新,或其它與之關聯的其餘effects的節點。effects list是finishedWork樹的子集。在workInProgress treecurrent tree中使用nextEffect屬性連接在一塊兒。

丹·阿布拉莫夫(Dan Abramov)將Effects list提供了一個比喻。將Fiber想象成一顆聖誕樹,用聖誕燈將全部有效的節點鏈接在一塊兒。

爲了可視化這一點,讓咱們想象下面的Fiber樹,其中高亮顯示的節點有一些「work」要作。

例如,咱們的更新致使將c2插入到DOM中,d2和c1更改屬性,b2觸發生命週期方法。 Effects list列表將把它們連接在一塊兒,這樣React就能夠遍歷時跳過其餘節點。

3.png

能夠看到具備effects的節點如何連接在一塊兒。當遍歷節點時,React使用firstEffect指針肯定列表的起始位置。上圖的Effects list能夠用下圖表示

4.png

Fiber節點樹的根

React應用都有一個或者多個充當容器的DOM元素

const domContainer = document.querySelector('#container');
ReactDOM.render(React.createElement(ClickCounter), domContainer);

React會爲容器建立FiberRoot對象,可使用容器的DOM引用訪問Fiber root對象:

// Fiber root對象
const fiberRoot = query('#container')._reactRootContainer._internalRoot

Fiber root是React保留對Fiber樹引用的地方,Fiber樹存儲在Fiber root對象的current屬性中

// Fiber樹
const hostRootFiberNode = fiberRoot.current

Fiber樹的第一個節點是一種特殊的類型節點,叫作HostRoot。它在內部建立,是最頂層組件的父組件。經過HostRoot節點的stateNode屬性能夠訪問FiberRoot節點.

// Fiber root對象
const fiberRoot = query('#container')._reactRootContainer._internalRoot
// hostRoot
const hostRootFiberNode = fiberRoot.current
// true
hostRootFiberNode.stateNode === fiberRoot

咱們能夠從HostRoot來訪問和探索整個Fiber樹。或者能夠經過組件的實例中得到單個Fiber節點

compInstance._reactInternalFiber

Fiber節點結構

ClickCounter組件的Fiber節點結構:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    alternate: null,
    key: null,
    updateQueue: null,
    memoizedState: {count: 0},
    pendingProps: {},
    memoizedProps: {},
    tag: 1,
    effectTag: 0,
    nextEffect: null
}

span DOM元素的Fiber節點結構:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    alternate: null,
    key: "2",
    updateQueue: null,
    memoizedState: null,
    pendingProps: {children: 0},
    memoizedProps: {children: 0},
    tag: 5,
    effectTag: 0,
    nextEffect: null
}

Fiber節點上有不少字段。咱們以前已經描述了alternate(備份節點),effectTag(記錄與之關聯的effects), nextEffect(鏈接具備effects的Fiber節點使其成爲線性節點)

stateNode屬性

保留對class組件實例的引用, DOM節點或其餘與Fiber節點相關聯的React元素類實例的引用。通常來講, 咱們能夠說這個屬性被用於保存與當前Fiber相關的本地狀態。

type屬性

定義與此Fiber節點相關聯的函數或者類。對於class組件,type屬性指向構造函數。對於DOM元素,type屬性指向HTML標記。我常常用這個字段來判斷這個Fiber節點與那個元素相關。

tag屬性

定義Fiber節點的類型。在協調期間使用它肯定須要作的"work"。如以前所述"work"取決於React元素的類型。createFiberFromTypeAndProps函數將React元素映射成相對應的Fiber節點類型。

在咱們的例子中。ClickCounter的tag爲1,表示爲ClassComponent。span的tag爲5,標記爲HostComponent。

updateQueue屬性

state更新和回調,DOM更新的隊列。

memoizedState屬性

用於建立輸出Fiber的state。在處理更新的時候,它映射的是當前界面上呈現的state。

memoizedProps屬性

在上一次渲染過程當中用來建立輸出的Fiber props。

pendingProps屬性

已經更新後的Fiber props。須要用於子組件和DOM元素。

key屬性

一組children中的惟一標示。幫助React肯定那些發生了更改,新增或刪除。更詳細的解釋在這裏

總結

完整的Fiber結構, 能夠在這裏看到,在上面的說明省略了不少的字段好比child,sibling並return。這三個字段是構成鏈表樹結構的關鍵。以及expirationTime、childExpirationTime和mode,這些字段是特定於Scheduler的。

通用算法

React分兩個階段執行work:render(渲染)和 commit(提交)

在render(渲染)階段,React將更新應用於經過setState或React.render調度的組件, 並找出須要在UI中更新的內容。

若是是初始渲染,React將爲render方法返回的每個元素建立新的Fiber節點。在以後的更新中,將從新使用和更新現有的Fiber節點。

render階段會構建一個帶有side-effects(反作用)的Fiber節點樹。effects描述了下一個commit(提交)階段須要完成的「work」。在commit(提交)階段,React會使用標記有effects的Fiber節點並將其應用於實例上。遍歷Effects list執行DOM更新和其餘對用戶可見的更改。

請切記,render階段的工做是能夠異步執行的,React根據可用時間處理一個或者多個Fiber節點。當發生一些更重要的事情時,React會中止並保存已完成的工做。等重要的事情處理完成後,React從中斷處繼續完成工做。可是有時可能會放棄已經完成的工做,從頂層從新開始。此階段執行的工做是對用戶是不可見的,所以能夠實現暫停。可是在commit(提交)階段始終是同步的它會產生用戶可見的變化, 例如DOM的修改. 這就是React須要一次性完成它們的緣由。

調用生命週期函數使用React的「work」之一。在render階段調用這些生命週期方法:

  • UNSAFE_componentWillMount (deprecated)
  • UNSAFE_componentWillReceiveProps (deprecated)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • UNSAFE_componentWillUpdate (deprecated)
  • render

因爲render階段不會產生DOM更新之類的反作用,所以React能夠異步地對組件進行異步處理更新(甚至可能在多個線程中進行)。可是帶有UNSAFE_前綴的生命週期函數經常會被誤用,開發者會把反作用添加到這些生命週期函數中。這可能會致使異步渲染出現問題

在commit階段調用這些生命週期方法,這些生命週期方法在commit階段執行,因此它們可能包含反作用並涉及DOM更新。

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

渲染(render)階段

協調算法使用renderRoot函數從最頂層的HostRoot節點開始,跳過已經處理過的節點,直到找到work未完成的節點爲止。例如, 當在組件樹深處調用setState方法, React 從頂部開始快速的跳過全部父級節點直接得到調用setState方法的組件。

WorkLoop

全部的Fiber節點在render階段都會在WorkLoop中被處理。這是循環同步部分的實現:

function workLoop(isYieldy) {
  if (!isYieldy) {
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {...}
}

在上面的代碼中,nextUnitOfWork保持了對workInProgress tree中一個有工做要處理的Fiber節點的引用。在React遍歷Fiber樹時,會使用nextUnitOfWork判斷是否有未完成"work"的Fiber節點。當節點處理完成「work」後,nextUnitOfWork會指向下一個Fiber節點的引用或者爲null。當nextUnitOfWork爲null時,React會退出WorkLoop,並準備進入到commit階段。

有四個主要的方法用於遍歷樹,並啓動或完成工做:

爲了演示如何使用它們。請查看下面遍歷Fiber樹的演示動畫。演示動畫中使用了這些函數的簡化實現。咱們能夠經過演示看到,首先處理子節點的「work」,而後處理父節點的「work」。

5.gif

使用直線鏈接表明同級,使用折線鏈接的代筆子級

逐步拆分下React遍歷Fiber樹的過程(首先處理子節點的「work」,而後處理父節點的「work」):

  1. beginWork a1
  2. beginWork b1,completeWork b1
  3. beginWork b2
  4. beginWork c1
  5. beginWork d1, completeWork d1
  6. beginWork d2, completeWork d2
  7. completeWork c1
  8. completeWork b2
  9. beginWork b3
  10. beginWork c2,completeWork c2
  11. completeWork b3
  12. completeWork a1

這個是視頻的鏈接, 從概念上將"begin"當作進入組件,「complete」當作離開組件。

咱們首先看下beginWork和performUnitOfWork這兩個函數:

function performUnitOfWork(workInProgress) {
    let next = beginWork(workInProgress);
    if (next === null) {
        next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {
    console.log('work performed for ' + workInProgress.name);
    return workInProgress.child;
}

performUnitOfWork從workInProgress tree中接收一個Fiber節點。而後調用beginWork開始處理Fiber節點的work。爲了演示,這裏只是log了Fiber節點的name字段表示work已經完成。函數beginWork老是返回指向循環中下一個子節點或null。

若是有下一個子節點, 它將在workLoop函數中分配給nextUnitOfWork。若是沒有子節點,React就知道了到達了分支的結尾。就會完成當前Fiber節點的work。React會執行它兄弟節點的工做,最後回溯到父節點。這是在completeUnitOfWork中完成的。

function completeUnitOfWork(workInProgress) {
    while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {
            // 若是有同級,則返回它。以繼續執行同級的工做
            return siblingFiber;
        } else if (returnFiber !== null) {
            // 回溯到上一級
            workInProgress = returnFiber;
            continue;
        } else {
            // 已經到了root節點
            return null;
        }
    }
}

function completeWork(workInProgress) {
    console.log('work completed for ' + workInProgress.name);
    return null;
}

當workInProgress節點沒有子節點時,會進入此函數。在完成當前Fiber的工做後,會檢查是否有兄弟節點。若是有,返回同級的兄弟節點的指針,分配給nextUnitOfWork。React將會從兄弟節點開始工做。只有處理完子節點全部分支以後, 纔會回溯到父節點(全部子節點處理完成後,纔會回溯到父節點)。

從實現能夠看出,completeUnitOfWork主要用於迭代,主要工做都是beginWork和completeWork函數中進行的。

完整的示例

這裏是完整的示例(beginWork,performUnitOfWork,completeUnitOfWork,completeWork的簡易實現)

// 首先構建鏈表樹
const a1 = {name: 'a1', child: null, sibling: null, return: null};
const b1 = {name: 'b1', child: null, sibling: null, return: null};
const b2 = {name: 'b2', child: null, sibling: null, return: null};
const b3 = {name: 'b3', child: null, sibling: null, return: null};
const c1 = {name: 'c1', child: null, sibling: null, return: null};
const c2 = {name: 'c2', child: null, sibling: null, return: null};
const d1 = {name: 'd1', child: null, sibling: null, return: null};
const d2 = {name: 'd2', child: null, sibling: null, return: null};
a1.child = b1;
b1.sibling = b2;
b2.sibling = b3;
b2.child = c1;
b3.child = c2;
c1.child = d1;
d1.sibling = d2;
b1.return = b2.return = b3.return = a1;
c1.return = b2;
d1.return = d2.return = c1;
c2.return = b3;

// 當前的指針是a1
let nextUnitOfWork = a1;
workLoop();

// 開始工做循環
function workLoop() {
    while (nextUnitOfWork !== null) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
}

function performUnitOfWork(workInProgress) {
    let next = beginWork(workInProgress);
    if (next === null) {
        next = completeUnitOfWork(workInProgress);
    }
    return next;
}

function beginWork(workInProgress) {
    log('work performed for ' + workInProgress.name);
    return workInProgress.child;
}

function completeUnitOfWork(workInProgress) {
    while (true) {
        let returnFiber = workInProgress.return;
        let siblingFiber = workInProgress.sibling;

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {
            return siblingFiber;
        } else if (returnFiber !== null) {
            workInProgress = returnFiber;
            continue;
        } else {
            return null;
        }
    }
}

function completeWork(workInProgress) {
    log('work completed for ' + workInProgress.name);
    return null;
}

function log(message) {
  let node = document.createElement('div');
  node.textContent = message;
  document.body.appendChild(node);
}

提交(commit)階段

提交階段從completeRoot開始。這是React更新DOM,調用getSnapshotBeforeUpdate,componentDidMount,componentDidUpdate,componentWillUnmount等生命週期的地方。

React進入這一階段時,有兩顆樹(workInProgress tree和current tree)以及effects list。current tree表示了當前屏幕上呈現的狀態。render階段遍歷current tree時會生成另外一顆樹,在源碼中被稱爲finishWork或workInProgress,表示將來須要在屏幕上呈現的狀態。workInProgress treecurrent tree結構相似。

調試時,如何獲取 current tree以及 workInProgress tree?
// current tree
// 從容器對象上獲取FiberRoot對象
const fiberRoot = query('#container')._reactRootContainer._internalRoot
// 獲取current tree
const currentTree = fiberRoot.current
// 獲取workInProgress tree
const workInProgressTree = fiberRoot.current.alternate

提交(commit)階段,主要執行commitRoot函數,執行如下的操做:

  1. 在有Snapshot標記的Fiber節點上調用getSnapshotBeforeUpdate生命週期方法。
  2. 在有Deletion標記的Fiber節點上調用componentWillUnmount生命週期方法。
  3. 執行全部的 DOM 插入, 更新, 刪除。
  4. workInProgress tree設置爲current tree
  5. 在有Placement標記的Fiber節點上調用componentDidMount生命週期方法。
  6. 在有Update標記的Fiber節點上調用componentDidUpdate生命週期方法。

在調用getSnapshotBeforeUpdate方法後,React將commit,Fiber樹中全部的反作用。分爲兩步:

第一步,執行全部的DOM插入,更新,刪除和ref卸載。而後將workInProgress tree設置爲current tree樹。這是在第一步完成以後,第二步以前完成的。所以在componentWillUnmount生命週期方法在執行期間,狀態依然是更新以前的。而componentDidMount/componentDidUpdate執行時的狀態是更新以後的。第二步,執行其餘生命週期方法和ref回調,這些方法做爲單獨的過程被執行。

commitRoot方法的預覽:

function commitRoot(root, finishedWork) {
    // 用來執行getSnapshotBeforeUpdate
    commitBeforeMutationLifecycles()
    // 用戶更新DOM,以及執行componentWillUnmount
    commitAllHostEffects();
    root.current = finishedWork;
    // 調用componentDidUpdate和componentDidMount生命週期的地方
    commitAllLifeCycles();
}

這些子函數,內部都包含了一個循環。循環遍歷effects list,並檢查effects的類型。當發現類型和子函數的目的相同時,就應用它。

Pre-mutation lifecycle methods

遍歷effects list,並檢查節點是否具備Snapshot effect的源代碼:

function commitBeforeMutationLifecycles() {
    while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
        if (effectTag & Snapshot) {
            const current = nextEffect.alternate;
            commitBeforeMutationLifeCycles(current, nextEffect);
        }
        nextEffect = nextEffect.nextEffect;
    }
}

若是是class組件,調用getSnapshotBeforeUpdate生命週期方法。

DOM updates

commitAllHostEffects是React執行DOM更新的地方。React會把componentWillUnmount做爲commitDeletion刪除過程當中的一部分。

function commitAllHostEffects() {
    switch (primaryEffectTag) {
        case Placement: {
            commitPlacement(nextEffect);
            ...
        }
        case PlacementAndUpdate: {
            commitPlacement(nextEffect);
            commitWork(current, nextEffect);
            ...
        }
        case Update: {
            commitWork(current, nextEffect);
            ...
        }
        case Deletion: {
            commitDeletion(nextEffect);
            ...
        }
    }
}

Post-mutation lifecycle methods

commitAllLifecycles是React調用全部剩餘生命週期方法componentDidUpdate和componentDidMount的地方。

總結

React源碼很複雜,Max Koretskyi的這篇文章內容也不少,全部總結下這篇博客的要點:

  1. React的核心是跟蹤組件狀態變化並將更新後的狀態更新到屏幕上。在React中,咱們把這個過程稱爲 reconciliation (協調)。
  2. 協調期間的各類活動在Fiber架構中統稱爲work(工做),work的類型取決於React元素的類型,React元素的類型取決於React.createElement函數的第一個參數類型。
  3. 協調期間React元素會被合併到Fiber節點樹之中,每一種React元素都會一種對應的Fiber節點。Fiber節點不會重複建立,會在第一次渲染後重用。
  4. React會爲每個React元素建立一個Fiber節點,咱們會獲得一個Fiber節點樹。Fiber節點樹是經過鏈表的形式存儲的。每個Fiber節點都擁有child,sibling和return字段,用於中斷恢復遍歷。
  5. 在初次渲染時會生成一棵Fiber樹。被稱爲current tree。當開始更新時,React會構建一個workInProgress tree
  6. current tree表明了當前的狀態,workInProgress tree表明了將來的狀態。
  7. 在commit階段workInProgress tree會被設置爲current tree
  8. React在遍歷current tree時,會爲每個Fiber節點建立一個alternate字段,alternate字段保存了Fiber節點的備份,alternate字段上保存的備份Fiber節點構成了workInProgress tree
  9. 數據獲取、訂閱或者手動修改過DOM都統稱爲反作用(side-effects)或者簡稱爲「做用」(effects)。由於它們會影響其餘組件,而且在渲染期間沒法完成。
  10. effects是一種work類型。所以Fiber節點是一種跟蹤更新和effects的便捷機制,每個Fiber節點都有與之相關聯的effects。它們被編碼在effectTag字段之中。Fiber中的effects定義了處理更新以後須要作的"work"。對於DOM元素,"work"包含了添加,更新,刪除。對於類組件,包括了更新ref,調用componentDidMount和componentDidUpdate生命週期方法。還有其餘effects對應於其餘類型的Fiber。
  11. React會將具備effects的Fiber節點,構建爲線性列表,以方便快速迭代。firstEffect是列表的開始位置,使用nextEffect屬性將節點連接在一塊兒(構建成線性列表)。
  12. 能夠經過容器DOM元素,獲取FiberRoot對象query('#container')._reactRootContainer._internalRoot
  13. FiberRoot對象的current屬性,是current tree的第一個節點,被稱爲hostRoot。fiberRoot.current
  14. hostRoot節點的stateNode屬性,指向FiberRoot對象。
  15. 獲取workInProgress tree, fiberRoot.current.alternate
  16. Fiber節點結構見上文。
  17. React分兩個階段執行work:render(渲染)和 commit(提交)
  18. render階段的工做是能夠異步執行的,React根據可用時間處理一個或者多個Fiber節點。當發生一些更重要的事情時,React會中止並保存已完成的工做。等重要的事情處理完成後,React從中斷處繼續完成工做。可是有時可能會放棄已經完成的工做,從頂層從新開始。此階段執行的工做是對用戶是不可見的,所以能夠實現暫停。
  19. commit(提交)階段始終是同步的它會產生用戶可見的變化, 例如DOM的修改. 這就是 React須要一次性完成它們的緣由。
  20. 全部的Fiber節點在render階段都會在WorkLoop中被處理。WorkLoop中主要有四個函數,performUnitOfWork,beginWork,completeUnitOfWork,completeWork。WorkLoop首先處理子節點的「work」,全部子節點處理完成後,回溯而後處理父節點的「work」。從概念上將"begin"當作進入組件,「complete」當作離開組件。
  21. 提交(commit)階段分爲兩步實現。第一步,執行全部的DOM插入,更新,刪除和ref卸載以及執行componentWillUnmount生命週期方法。第二步,執行其餘生命週期方法和ref回調,這些方法做爲單獨的過程被執行。第一步和第二步中間會將workInProgress tree設置爲current tree樹。

參考

相關文章
相關標籤/搜索