「譯」React Fiber 那些事: 深刻解析新的協調算法

翻譯自:Inside Fiber: in-depth overview of the new reconciliation algorithm in Reactjavascript

React 是一個用於構建用戶交互界面的 JavaScript 庫,其核心 機制 就是跟蹤組件的狀態變化,並將更新的狀態映射到到新的界面。在 React 中,咱們將此過程稱之爲協調。咱們調用 setState 方法來改變狀態,而框架自己會去檢查 state 或 props 是否已經更改來決定是否從新渲染組件。html

React 的官方文檔對 協調機制 進行了良好的抽象描述: React 的元素、生命週期、 render 方法,以及應用於組件子元素的 diffing 算法綜合起到的做用,就是協調。從 render 方法返回的不可變的 React 元素一般稱爲「虛擬 DOM」。這個術語有助於早期向人們解釋 React,但它也引發了混亂,而且再也不用於 React 文檔。在本文中,我將堅持稱它爲 React 元素的樹。java

除了 React 元素的樹以外,框架老是在內部維護一個實例來持有狀態(如組件、 DOM 節點等)。從版本 16 開始, React 推出了內部實例樹的新的實現方法,以及被稱之爲 Fiber 的算法。若是想要了解 Fiber 架構帶來的優點,能夠看下 React 在 Fiber 中使用鏈表的方式和緣由react

這是本系列的第一篇文章,這一系列的目的就是向你描繪出 React 的內部架構。在本文中,我但願可以提供一些與算法相關的重要概念和數據結構,並對其進行深刻闡述。一旦咱們有足夠的背景,咱們將探索用於遍歷和處理 Fiber 樹的算法和主要功能。本系列的下一篇文章將演示 React 如何使用該算法執行初始渲染和處理 state 以及 props 的更新。到那時,咱們將繼續討論調度程序的詳細信息,子協調過程以及構建 effect 列表的機制。git

我將給你帶來一些很是高階的知識🧙。我鼓勵你閱讀來了解 Concurrent React 的內部工做的魔力。若是您計劃開始爲 React 貢獻代碼,本系列也將爲您提供很好的指導。我是 逆向工程的堅決信徒,所以本文會有不少最新版本 16.6.0 中的源代碼的連接。github

須要消化的內容絕對是不少的,因此若是你當下還不能很理解的話,不用感到壓力。花些時間是值得的。請注意,只是使用 React 的話,您不須要知道任何文中的內容。本文是關於 React 在內部是如何工做的。算法

我在 ag-Grid 擔任開發人員倡導者。若是您想了解數據網格或尋找最終的 React 數據網格解決方案,請與咱們聯繫或嘗試使用指南「在5分鐘內開始使用 React 網格」。我很樂意回答您可能會有的任何問題。vim

背景介紹

以下是我將在整個系列中使用的一個簡單的應用程序。咱們有一個按鈕,點擊它將會使屏幕上渲染的數字加 1:數組

而它的實現以下:數據結構

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>
        ]
    }
}

你能夠在 這裏 把玩一下。如您所見,它就是一個可從 render 方法返回兩個子元素 -- buttonspan 的簡單組件。只要你單擊該按鈕,組件的狀態將在處理程序內被更新,而狀態的更新就會致使 span 元素內的文本更新。

在協調階段內,React 進行了各類各樣的活動。例如,在咱們的簡單應用程序中,從第一次渲染到狀態更新後的期間內,React 執行了以下高階操做:

  • 更新了 ClickCounter 組件的內部狀態的 count 屬性
  • 獲取和比較了 ClickCounter 組件的子組件及其 props
  • 更新 span 元素的 props
  • 更新 span 元素的 textContent 屬性

除了上述活動,React 在協調期間還執行了一些其餘活動,如調用 生命週期方法 或更新 refs全部這些活動在 Fiber 架構中統稱爲「工做」。工做類型一般取決於 React 元素的類型。例如,對於類定義的組件,React 須要建立實例,可是函數定義的組件就沒必要執行此操做。正如咱們所瞭解的,React 中有許多元素類型,例如:類和函數組件,宿主組件(DOM 節點)portal 等。React 元素的類型由 createElement 函數的第一個參數定義,此函數一般在 render 方法中調用以建立元素。

在咱們開始探索活動細節和 Fiber 算法的主要內容以前,咱們首先來熟悉下 React 在內部使用的一些數據結構。

從 React 元素到 Fiber 節點

React 中的每一個組件都有一個 UI 表示,咱們能夠稱之爲從 render 方法返回的一個視圖或模板。這是 ClickCounter 組件的模板:

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

React 元素

若是模板通過 JSX 編譯器處理,你就會獲得一堆 React 元素。這是從 React 組件的 render 方法返回的,但並非 HTML 。因爲咱們並無被強制要求使用 JSX,所以咱們的 ClickCounter 組件的 render 方法能夠像這樣重寫:

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

render 方法中調用的 React.createElement 會產生兩個以下的數據結構:

[
    {
        $$typeof: Symbol(react.element),
        type: 'button',
        key: "1",
        props: {
            children: 'Update counter',
            onClick: () => { ... }
        }
    },
    {
        $$typeof: Symbol(react.element),
        type: 'span',
        key: "2",
        props: {
            children: 0
        }
    }
]

能夠看到,React 爲這些對象添加了 $$typeof 屬性,從而將它們惟一地標識爲 React 元素。此外咱們還有屬性 typekeyprops 來描述元素。這些值取自你傳遞給 React.createElement 函數的參數。請注意React 如何將文本內容表示爲 spanbutton 節點的子項,以及 click 鉤子如何成爲 button 元素 props 的一部分。 React 元素上還有其餘字段,如 ref 字段,而這超出了本文的範圍。

ClickCounter 的 React 元素就沒有什麼 props 或 key 屬性:

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

Fiber 節點

協調期間,從 render 方法返回的每一個 React 元素的數據都會被合併到 Fiber 節點樹中。每一個 React 元素都有一個相應的 Fiber 節點。與 React 元素不一樣,不會在每次渲染時從新建立這些 Fiber 。這些是持有組件狀態和 DOM 的可變數據結構。

咱們以前討論過,根據不一樣 React 元素的類型,框架須要執行不一樣的活動。在咱們的示例應用程序中,對於類組件 ClickCounter ,它調用生命週期方法和 render 方法,而對於 span 宿主組件(DOM 節點),它進行得是 DOM 修改。所以,每一個 React 元素都會轉換爲 相應類型 的 Fiber 節點,用於描述須要完成的工做。

您能夠將 Fiber 視爲表示某些要作的工做的數據結構,或者說,是一個工做單位。Fiber 的架構還提供了一種跟蹤、規劃、暫停和銷燬工做的便捷方式。

當 React 元素第一次轉換爲 Fiber 節點時,React 在 createFiberFromTypeAndProps 函數中使用元素中的數據來建立 Fiber。在隨後的更新中,React 會再次利用 Fiber 節點,並使用來自相應 React 元素的數據更新必要的屬性。若是再也不從 render 方法返回相應的 React 元素,React 可能還須要根據 key 屬性來移動或刪除層級結構中的節點。

查看 ChildReconciler 函數以查看 React 爲現有 Fiber 節點執行的全部活動和相應函數的列表。

由於React爲每一個 React 元素建立一個 Fiber 節點,而且由於咱們有一個這些元素組成的樹,因此咱們能夠獲得一個 Fiber 節點樹。對於咱們的示例應用程序,它看起來像這樣:

全部 Fiber 節點都經過鏈表鏈接,具體是使用Fiber節點上的 childsiblingreturn 屬性。至於它爲何以這種方式工做,若是您尚未閱讀過個人文章,更多詳細信息請查看 React 在 Fiber 中使用鏈表的方法和緣由

current 樹及 workInProgress 樹

在第一次渲染以後,React 最終獲得一個 Fiber 樹,它反映了用於渲染 UI 的應用程序的狀態。這棵樹一般被稱爲 current 樹(當前樹)。當 React 開始處理更新時,它會構建一個所謂的 workInProgress 樹(工做過程樹),它反映了要刷新到屏幕的將來狀態。

全部工做都在 workInProgress 樹的 Fiber 節點上執行。當 React 遍歷 current 樹時,對於每一個現有 Fiber 節點,React 會建立一個構成 workInProgress 樹的備用節點,這一節點會使用 render 方法返回的 React 元素中的數據來建立。處理完更新並完成全部相關工做後,React 將準備好一個備用樹以刷新到屏幕。一旦這個 workInProgress 樹在屏幕上呈現,它就會變成 current 樹。

React 的核心原則之一是一致性。 React 老是一次性更新 DOM - 它不會顯示部分中間結果。workInProgress 樹充當用戶不可見的「草稿」,這樣 React 能夠先處理全部組件,而後將其更改刷新到屏幕。

在源代碼中,您將看到不少函數從 currentworkInProgress 樹中獲取 Fiber 節點。這是一個這類函數的簽名:

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

每一個Fiber節點持有備用域在另外一個樹的對應部分的引用。來自 current 樹中的節點會指向 workInProgress 樹中的節點,反之亦然。

反作用

咱們能夠將 React 中的一個組件視爲一個使用 state 和 props 來計算 UI 表示的函數。其餘全部活動,如改變 DOM 或調用生命週期方法,都應該被視爲反作用,或者簡單地說是一種效果。文檔中 是這樣描述的:

您以前可能已經在 React 組件中執行數據提取,訂閱或手動更改 DOM。咱們將這些操做稱爲「反作用」(或簡稱爲「效果」),由於它們會影響其餘組件,而且在渲染過程當中沒法完成。

您能夠看到大多 state 和 props 更新都會致使反作用。既然使用反作用是工做(活動)的一種類型,Fiber 節點是一種方便的機制來跟蹤除了更新之外的效果。每一個 Fiber 節點均可以具備與之相關的反作用,它們可在 effectTag 字段中編碼。

所以,Fiber 中的反作用基本上定義了處理更新後須要爲實例完成的 工做。對於宿主組件(DOM 元素),所謂的工做包括添加,更新或刪除元素。對於類組件,React可能須要更新 refs 並調用 componentDidMountcomponentDidUpdate 生命週期方法。對於其餘類型的 Fiber ,還有相對應的其餘反作用。

反作用列表

React 處理更新的素對很是迅速,爲了達到這種水平的性能,它採用了一些有趣的技術。其中之一是構建具備反作用的 Fiber 節點的線性列表,從而可以快速遍歷。遍歷線性列表比樹快得多,而且沒有必要在沒有反作用的節點上花費時間。

此列表的目標是標記具備 DOM 更新或其餘相關反作用的節點。此列表是 finishedWork 樹的子集,並使用 nextEffect 屬性而不是 currentworkInProgress 樹中使用的 child 屬性進行連接。

Dan Abramov 爲反作用列表提供了一個類比。他喜歡將它想象成一棵聖誕樹,「聖誕燈」將全部有效節點捆綁在一塊兒。爲了使這個可視化,讓咱們想象以下的 Fiber 節點樹,其中標亮的節點有一些要作的工做。例如,咱們的更新致使 c2 被插入到 DOM 中,d2c1 被用於更改屬性,而 b2 被用於觸發生命週期方法。反作用列表會將它們連接在一塊兒,以便 React 稍後能夠跳過其餘節點:

能夠看到具備反作用的節點是如何連接在一塊兒的。當遍歷節點時,React 使用 firstEffect 指針來肯定列表的開始位置。因此上面的圖表能夠表示爲這樣的線性列表:

如您所見,React 按照從子到父的順序應用反作用。

Fiber 樹的根節點

每一個 React 應用程序都有一個或多個充當容器的 DOM 元素。在咱們的例子中,它是帶有 ID 爲 containerdiv 元素。React 爲每一個容器建立一個 Fiber 根 對象。您可使用對 DOM 元素的引用來訪問它:

const fiberRoot = query('#container')._reactRootContainer._internalRoot

這個 Fiber 根是React保存對 Fiber 樹的引用的地方,它存儲在 Fiber 根對象的 current 屬性中:

const hostRootFiberNode = fiberRoot.current

Fiber 樹以 一個特殊類型 的 Fiber 節點 HostRoot 開始。它在內部建立的,並充當最頂層組件的父級。HostRoot 節點可經過 stateNode 屬性返回到 FiberRoot

fiberRoot.current.stateNode === fiberRoot; // true

你能夠經過 Fiber 根訪問最頂層的 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 元素:

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

Fiber 節點上有不少字段。我在前面的部分中描述了字段 alternateeffectTagnextEffect 的用途。如今讓咱們看看爲何須要其餘字段。

stateNode

保存組件的類實例、DOM 節點或與 Fiber 節點關聯的其餘 React 元素類型的引用。總的來講,咱們能夠認爲該屬性用於保持與一個 Fiber 節點相關聯的局部狀態。

type

定義此 Fiber 節點的函數或類。對於類組件,它指向構造函數,對於 DOM 元素,它指定 HTML 標記。我常用這個字段來理解 Fiber 節點與哪一個元素相關。

tag

定義 Fiber 的類型。它在協調算法中用於肯定須要完成的工做。如前所述,工做取決於React元素的類型。函數 createFiberFromTypeAndProps 將 React 元素映射到相應的 Fiber 節點類型。在咱們的應用程序中,ClickCounter 組件的屬性 tag 是 1,表示是 ClassComponent(類組件),而 span 元素的屬性 tag 是 5,表示是 HostComponent(宿主組件)。

updateQueue

狀態更新、回調和 DOM 更新的隊列。

memoizedState

用於建立輸出的 Fiber 狀態。處理更新時,它會反映當前在屏幕上呈現的狀態。

memoizedProps

在前一個渲染中用於建立輸出的 Fiber 的 props。

pendingProps

已從 React 元素中的新數據更新而且須要應用於子組件或 DOM 元素的 props。

key

惟一標識符,當具備一組子元素的時候,可幫助 React 肯定哪些項發生了更改、添加或刪除。它與 此處 描述的 React 的「列表和鍵」功能有關。

您能夠在 此處 找到 Fiber 節點的完整結構。我在上面的解釋中省略了一堆字段。特別是,我跳過了指針 childsiblingreturn,它們構成了我在 上一篇文章 中描述的樹數據結構。以及專屬於 Scheduler 的 expirationTime,childExpirationTime 和 mode 等字段類別。

通用算法

React 在兩個主要階段執行工做:rendercommit

在第一個 render 階段,React 經過 setUpdateReact.render 計劃性的更新組件,並肯定須要在 UI 中更新的內容。若是是初始渲染,React 會爲 render 方法返回的每一個元素建立一個新的 Fiber 節點。在後續更新中,現有 React 元素的 Fiber 節點將被重複使用和更新。這一階段是爲了獲得標記了反作用的 Fiber 節點樹。反作用描述了在下一個commit階段須要完成的工做。在當前階段,React持有標記了反作用的 Fiber 樹並將其應用於實例。它遍歷反作用列表、執行 DOM 更新和用戶可見的其餘更改。

咱們須要重點理解的是,第一個 render 階段的工做是能夠異步執行的。React 能夠根據可用時間片來處理一個或多個 Fiber 節點,而後停下來暫存已完成的工做,並轉而去處理某些事件,接着它再從它中止的地方繼續執行。但有時候,它可能須要丟棄完成的工做並再次從頂部開始。因爲在此階段執行的工做不會致使任何用戶可見的更改(如 DOM 更新),所以暫停行爲纔有了意義。與之相反的是,後續 commit 階段始終是同步的。這是由於在此階段執行的工做會致使用戶可見的變化,例如 DOM 更新。這就是爲何 React 須要在一次單一過程當中完成這些更新。

React 要作的一種工做就是調用生命週期方法。一些方法是在 render 階段調用的,而另外一些方法則是在 commit 階段調用。這是在第一個 render 階段調用的生命週期列表:

  • [UNSAFE_]componentWillMount(棄用)
  • [UNSAFE_]componentWillReceiveProps(棄用)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • [UNSAFE_]componentWillUpdate(棄用)
  • render

正如你所看到的,從版本 16.3 開始,在 render 階段執行的一些保留的生命週期方法被標記爲 UNSAFE,它們如今在文檔中被稱爲遺留生命週期。它們將在將來的 16.x 發佈版本中棄用,而沒有 UNSAFE 前綴的方法將在 17.0 中移除。您能夠在 此處 詳細瞭解這些更改以及建議的遷移路徑。

那麼這麼作的目的是什麼呢?

好吧,咱們剛剛瞭解到,由於 render 階段不會產生像 DOM 更新這樣的反作用,因此 React 能夠異步處理組件的異步更新(甚至可能在多個線程中執行)。可是,標有 UNSAFE 的生命週期常常被誤解和濫用。開發人員傾向於將帶有反作用的代碼放在這些方法中,這可能會致使新的異步渲染方法出現問題。雖然只有沒有 UNSAFE 前綴的對應方法將被刪除,但它們仍可能在即將出現的併發模式(您能夠選擇退出)中引發問題。

接下來羅列的生命週期方法是在第二個 commit 階段執行的:

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

由於這些方法都在同步的 commit 階段執行,他們可能會包含反作用,並對 DOM 進行一些操做。

至此,咱們已經有了充分的背景知識,下面咱們能夠看下用來遍歷樹和執行一些工做的通用算法。

Render 階段

協調算法始終使用 renderRoot 函數從最頂層的 HostRoot 節點開始。不過,React 會略過已經處理過的 Fiber 節點,直到找到未完成工做的節點。例如,若是在組件樹中的深層組件中調用 setState 方法,則 React 將從頂部開始,但會快速跳過各個父項,直到它到達調用了 setState 方法的組件。

工做循環的主要步驟

全部的 Fiber 節點都會在 工做循環 中進行處理。以下是該循環的同步部分的實現:

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

在上面的代碼中,nextUnitOfWork 持有 workInProgress 樹中的 Fiber 節點的引用,這個樹有一些工做要作。當 React 遍歷 Fiber 樹時,它會使用這個變量來知曉是否有任何其餘 Fiber 節點具備未完成的工做。處理過當前 Fiber 後,變量將持有樹中下一個 Fiber 節點的引用或 null。在這種狀況下,React 退出工做循環並準備好提交更改。

遍歷樹、初始化或完成工做主要用到 4 個函數:

爲了演示他們的使用方法,咱們能夠看看以下展現的遍歷 Fiber 樹的動畫。我已經在演示中使用了這些函數的簡化實現。每一個函數都須要對一個 Fiber 節點進行處理,當 React 從樹上下來時,您能夠看到當前活動的 Fiber 節點發生了變化。從視頻中咱們能夠清楚地看到算法如何從一個分支轉到另外一個分支。它首先完成子節點的工做,而後才轉移到父節點進行處理。

注意,垂直方向的連線表示同層關係,而折線鏈接表示父子關係,例如,b1 沒有子節點,而 b2 有一個子節點 c1

在這個 視頻 中咱們能夠暫停播放並檢查當前節點和函數的狀態。從概念上講,你能夠將「開始」視爲「進入」一個組件,並將「完成」視爲「離開」它。在解釋這些函數的做用時,您也能夠在 這裏 使用示例和實現。

咱們首先開始研究 performUnitOfWorkbeginWork 這兩個函數:

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;
}

函數 performUnitOfWorkworkInProgress 樹接收一個 Fiber 節點,並經過調用 beginWork 函數啓動工做。這個函數將啓動全部 Fiber 執行工做所須要的活動。出於演示的目的,咱們只 log 出 Fiber 節點的名稱來表示工做已經完成。函數 beginWork 始終返回指向要在循環中處理的下一個子節點的指針或 null。

若是有下一個子節點,它將被賦值給 workLoop 函數中的變量 nextUnitOfWork。可是,若是沒有子節點,React 知道它到達了分支的末尾,所以它能夠完成當前節點。一旦節點完成,它將須要爲同層的其餘節點執行工做,並在完成後回溯到父節點。這是 completeUnitOfWork 函數執行的代碼:

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

        nextUnitOfWork = completeWork(workInProgress);

        if (siblingFiber !== null) {
            // If there is a sibling, return it
            // to perform work for this sibling
            return siblingFiber;
        } else if (returnFiber !== null) {
            // If there's no more work in this returnFiber,
            // continue the loop to complete the parent.
            workInProgress = returnFiber;
            continue;
        } else {
            // We've reached the root.
            return null;
        }
    }
}

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

你能夠看到函數的核心就是一個大的 while 的循環。當 workInProgress 節點沒有子節點時,React 會進入此函數。完成當前 Fiber 節點的工做後,它就會檢查是否有同層節點。若是找的到,React 退出該函數並返回指向該同層節點的指針。它將被賦值給 nextUnitOfWork 變量,React將從這個節點開始執行分支的工做。咱們須要着重理解的是,在當前節點上,React 只完成了前面的同層節點的工做。它還沒有完成父節點的工做。只有在完成以子節點開始的全部分支後,才能完成父節點和回溯的工做。

從實現中能夠看出,performUnitOfWorkcompleteUnitOfWork 主要用於迭代目的,而主要活動則在 beginWorkcompleteWork 函數中進行。在後續的系列文章中,咱們將瞭解隨着 React 進入 beginWorkcompleteWork 函數,ClickCounter 組件和 span 節點會發生什麼。

commit 階段

這一階段從函數 completeRoot 開始。在這個階段,React 更新 DOM 並調用變動生命週期以前及以後方法的地方。

當 React 進入這個階段時,它有 2 棵樹和反作用列表。第一個樹表示當前在屏幕上渲染的狀態,而後在 render 階段會構建一個備用樹。它在源代碼中稱爲 finishedWorkworkInProgress,表示須要映射到屏幕上的狀態。此備用樹會用相似的方法經過 childsibling 指針連接到 current 樹。

而後,有一個反作用列表 -- 它是 finishedWork 樹的節點子集,經過 nextEffect 指針進行連接。須要記住的是,反作用列表是運行 render 階段的結果。渲染的重點就是肯定須要插入、更新或刪除的節點,以及哪些組件須要調用其生命週期方法。這就是反作用列表告訴咱們的內容,它頁正是在 commit 階段迭代的節點集合。

出於調試目的,能夠經過 Fiber 根的屬性 current 訪問 current 樹。能夠經過 current 樹中 HostFiber 節點的 alternate 屬性訪問 finishedWork 樹。

commit 階段運行的主要函數是 commitRoot 。它執行以下下操做:

  • 在標記爲 Snapshot 反作用的節點上調用 getSnapshotBeforeUpdate 生命週期
  • 在標記爲 Deletion 反作用的節點上調用 componentWillUnmount 生命週期
  • 執行全部 DOM 插入、更新、刪除操做
  • finishedWork 樹設置爲 current
  • 在標記爲 Placement 反作用的節點上調用 componentDidMount 生命週期
  • 在標記爲 Update 反作用的節點上調用 componentDidUpdate 生命週期

在調用變動前方法 getSnapshotBeforeUpdate 以後,React 會在樹中提交全部反作用,這會經過兩波操做來完成。第一波執行全部 DOM(宿主)插入、更新、刪除和 ref 卸載。而後 React 將 finishedWork 樹賦值給 FiberRoot,將 workInProgress 樹標記爲 current 樹。這是在提交階段的第一波以後、第二波以前完成的,所以在 componentWillUnmount 中前一個樹仍然是 current,在 componentDidMount/Update 期間已完成工做是 current。在第二波,React 調用全部其餘生命週期方法和引用回調。這些方法單獨傳遞執行,從而保證整個樹中的全部放置、更新和刪除可以被觸發執行。

如下是運行上述步驟的函數的要點:

function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles()
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}

這些子函數中都實現了一個循環,該循環遍歷反作用列表並檢查反作用的類型。當它找到與函數目的相關的反作用時,就會執行。

更新前的生命週期方法

例如,這是在反作用樹上遍歷並檢查節點是否具備 Snapshot 反作用的代碼:

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

對於一個類組件,這一反作用意味着會調用 getSnapshotBeforeUpdate 生命週期方法。

DOM 更新

commitAllHostEffects 是 React 執行 DOM 更新的函數。該函數基本上定義了節點須要完成的操做類型,並執行這些操做:

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

有趣的是,React 調用 componentWillUnmount 方法做爲 commitDeletion 函數中刪除過程的一部分。

更新後的生命週期方法

commitAllLifecycles 是 React 調用全部剩餘生命週期方法的函數。在 React 的當前實現中,惟一會調用的變動方法就是 componentDidUpdate

咱們終於講完了。讓我知道您對該文章的見解或在評論中提出問題。我還有關於調度程序、子調和過程以及如何構建反作用列表的文章來對這些內容提供深刻的解釋。我還計劃製做一個視頻,在這裏我將展現如何使用本文做爲基礎來調試應用程序。

文章可隨意轉載,但請保留此 原文連接
很是歡迎有激情的你加入 ES2049 Studio,簡歷請發送至 caijun.hcj(at)alibaba-inc.com 。
相關文章
相關標籤/搜索