(譯)深刻 React 協調算法 Reconciliation

原文引用:in-depth overview of the new reconciliation algorithm in React
做者:Max Koretskyijavascript


前言

React  是一個用於構建用戶界面的 JavaScript  庫。它的核心原理是追蹤組件中的狀態的改變,而後將這些被更新的狀態自動刷新到屏幕上。在 React 中有一個過程被稱之爲 協調 reconciliation 。也就是當咱們調用  setState  方法,或是框架檢測到 state or props變化後,便開始從新開始計算比對組件的先後狀態,並渲染與之對應的改變的 UI。

React 官方文檔提供了對其原理的高階概述React Element ,生命週期方法,render方法,組件孩子節點的 diff算法的應用等 在 React中所扮演的角色。其中 render 方法所返回的 React elements Tree就是咱們經常提到的  虛擬DOM virtual DOM 。這個術語有助於前期向人們解釋 React ,可是它也會形成一些困惑,由於任何 React 的官方文檔中都沒出現過它。因此在這篇文章中,我將堅決稱它爲  React elements 樹。

除之外,React 還有另一棵內部實例樹(組件實例,或 DOM 節點等),它被用來保存 state 狀態。從 React16 開始 React 推出了新的內部實例樹的實現 以及它的管理算法 統稱爲 Fiber。 
html

背景知識

下面是一個很是簡單的小應用,接下來的整篇文章都將使用到它。一個按鈕,點擊數字加一,並渲染到屏幕上。java

example.gif

代碼:

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 方法中返回了兩個元素   button 和 span 。當你快速點擊按鈕的時候,組件的狀態會在內部更新。這時 span 的文本內容也隨之更新。node

在 React 協調過程當中有各類各樣的工做須要被執行。以上面代碼爲例,在 React 第一次渲染和隨後的 state 更新期間的作了一些事情,大體以下:react

  • 更新  ClickCounter  組件 state  的  count 屬性。
  • 檢索  ClickCounter  組件的孩子節點並對比他們的 props
  • 更新 span元素。

還有一些在協調階段執行的工做,如:生命週期方法調用或 refs  更新等。全部這些活動在 Fiber 架構中被總稱爲 ‘work’‘work’ 的類型一般基於 React Elementtype。 例如,對類組件來講須要 React 來建立實例,相對於函數組件來講就沒有必要了。衆所周知,在 React 中有很的組件類型,類組件,函數組件(無狀態組件),宿主組件(DOM),portals等。React Elementtype由 createElement 函數的第一個參數定義。這個函數一般在 render方法中被使用,用於建立上面提到的 React elementsgit

在咱們探索 ‘work’ 和  Fiber  架構算法以前,咱們先熟悉下一在 React 內部使用的數據結構。github

從 React Element 到 Fiber Node

React 中每個組件都有其對應的 UI 呈現,咱們稱之爲視圖,或者也能夠說是 render 方法返回的模板。例子 ClickCounter  組件的模板以下:算法

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

React Element

當一個模板被傳入到 JSX 編譯器,最終會生成 React Element。它其實就是 React 組件的 render 方法返回的實際內容,並非 HTML..,固然咱們也能夠不使用 JSX 語法,也能夠直接像下面代碼示例中展現那樣寫組件 ,能接受的話,哈哈哈……:json

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

這裏調用的  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](https://overreacted.io/why-do-react-elements-have-typeof-property/) 屬性,它在後會用到,主要是用來甄別是否爲合法有效的 Element。同時咱們可看到了typekey and props三個屬性。這裏咱們須要要注意一下 React 是如何表示 spanbutton 兩個節點的文本內容的,還有 button 節點的 onClick 部分。

對於示例組件 **ClickCounter** 來講,它自身的 React Element 並無添加任何屬性 或者 key

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

Fiber nodes

協調其實是將從組件 render 方法返回的每一個 React Element 合併進入fiber node 樹的過程。每個 React Element 都有一個對應的 fiber 節點。不一樣於  React Elementfiber 並不會在每次渲染時候都從新建立。它們是可變的數據結構,保存着組件的狀態,以及對應實體 DOM 節點的引用,這個下面會講到。

咱們以前提到過,React 會基於 React Elementtype 屬性執行不一樣工做。在示例程序中, ClickCounter  組件將調用生命週期函數 和 render 方法,而宿主組件  span  將會改變 DOM 內容。每個 React Element  都會轉換爲它所對應的 Fiber Node,用於描述接下來要被執行的工做。

也就是說,能夠認爲 fiber 節點就是描述隨後要執行工做的一種數據結構,能夠類比於調用棧中的幀 ,換而言之,就是一個工做單元。fiber 架構同時還提供了便捷的方式去追蹤調度暫停中斷協調進程。

當一個 React Element 第一次被轉換爲 fiber 節點的時候,React 將會從 React Element 種提取數據子並在在 createFiberFromTypeAndProps 函數中建立一個新的 fiber。隨後的更新過程當中 React會複用這個建立的 fiber 節點,並將對應 React Element  被改變數據更新到這個 fiber 節點上。React 也會移除一些 fiber節點,例如:當同層級上對應 key屬性改變時,或 render 方法返回的  React Element 沒有該 fiber 對應的 Element 對象時,該 fiber 就會被刪除。

由於 React 會爲每一個獲得的 React Element 建立 fiber,這些 fiber 節點被鏈接起來組成 fiber tree。在咱們的例子中它是這樣的:
 

fiberTree.png

全部的 fiber 節點都經過屬性 childsibling and return 鏈接起來。

current 和 workInProgress 變量

在第一次渲染完成後,React 最終生成 fiber tree,它能夠理解爲渲染出的 UI 界面的在內存中的映射。fiber tree 的引用變量一般是 current。當 React 開始更新操做時,它會又會構建一棵被稱爲 workInProgress 的新樹。workInProgress 接下來將會替換 current變量所引用的舊的 fiber tree,而後隨之會被刷新到屏幕上。

全部執行相關更新,刪除等操做的 fibers 都來自於  workInProgress 樹。當 React 遍歷  current 樹的時候,會爲每一個 fiber node 建立一個備用節點,這些備用節點最終組成整個  workInProgress 樹。這些被新建立的 fibers 的數據來自於 render 方法返回的 React Element 。一旦更新操做的工做完成,React 將會擁有一顆隨時即可刷新到屏幕的備用樹。當  workInProgress 樹被刷新到屏幕後,那麼它就會變成  current 樹。也就是說 current 變量會保存這顆新樹的指針。

React  的一個核心原則就是一致性。因此 React 會一次性遍更新全部須要處理的 DOM,不會只顯示部分結果。 workInProgress tree 對用戶而言相似於一個不可見的草稿。因此 React 會先處理全部的組件內部狀態的更新,而後再將它們改變了的部分從新刷新到屏幕上。

在源碼中你會看到不少的函數從  current and workInProgress 樹 中獲取 fiber 節點。每個 fiber 節點在  alternate 域中保存它在另外一棵樹中對應節點的引用。也就是說一個來自  current 樹的節點會指向  workInProgress 樹中相對應的節點,反之亦然。

反作用

咱們能夠認爲一個 React 組件,它其實就是一個使用 stateprops 來計算 UI 表現形式的函數。像 DOM 或 調用生命週期方法這樣的活動都被認爲是一個反作用。關於反作用的詳情能夠查看官方文檔

平常開發中,其實不少時候 state  和 props 的更新都會致使反作用的產生。每種反作用的應用其實是一種指定類型的工做,而 fiber 節點是一種方便的機制去追蹤反作用,從它被添加直到被應用。每個 fiber node 都有會有與之相關的反作用。它們都被編碼在fiber 節點的 effectTag域上。

因此,Fiber中的反作用基本上能夠理解爲,定義了在更新進程完成後須要對實例執行的具體操做。對宿主組件而言,也就是 DOM ,的這些工做包括 element 的添加,更新,移除。對的類組件來講可能須要更新 refs 以及調用生命週期方法等。

反作用列表

ReactDOM 更新速度很是快,爲了實現這樣的性能,它的實現上採用了一些有趣的技術,其中之一就是構建了一個能夠高效迭代遍歷的線性 fiber node 列表,該列表中全部 fiber node 都是有反作用須要被執行的,對於沒有反作用的 fiber node  則沒必要浪費寶貴的時間了。

這個列表的目的是標記那些須要執行各類反作用的節點,包括 DOM 更新,刪除,生命週期函數調用等。它同時也是  finishedWork 樹的子集,列表中的節點之間經過  nextEffect 屬性鏈接。

Dan Abramov 對反作用列表作了一個有趣的比喻,他認爲反作用列表像一顆聖誕樹,全部的反作用節點被導線綁在一塊兒。以下圖:

effectslist.png


能夠看到,有反作用的節點被都鏈接在一塊兒。當遍歷這些節點的時候, React 經過  firstEffect 指針獲得列表的起點。因此上面的圖也能夠理解爲這樣:

effectsline.png


經過觀察,能夠發現 React 應用反作用的順序是從子節點到父節點。

fiber tree 的根節點

每個 React 應用都有一個或多個 DOM 元素做爲容器,一般開發中那個 idroot 的元素。示例程序中的容器是 id 爲 containerdiv

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


React 會爲每個容器建立一個 fiber root 對象。 進入這個地址能夠看到它的具體實現。

const fiberRoot = query('#container')._reactRootContainer._internalRoot
複製代碼

fiber rootReact 保存  fiber tree 引用的地方。也就是 fiber root 節點上的屬性  current 。

const hostRootFiberNode = fiberRoot.current
複製代碼

fiber tree 起點是一個被稱之爲 HostRoot  的特殊類型的 fiber node。它在內部被建立,扮演着全部節點的祖先節點的角色。HostRoot 的屬性 stateNode 反過來又指向 FiberRoot 。

fiberRoot.current.stateNode === fiberRoot; // true
複製代碼

你能夠探索你本身應用的中的 fiber tree 經過進入頂層的  fiberRoot ,進而獲得  HostRoot 。或者你也能夠經過組件實例獲得單個 fiber 節點。

compInstance._reactInternalFiber
複製代碼

Fiber node 結構

如今讓咱們瞥一眼建立的 ClickCounter 組件的 fiber node。

{
    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 HTMLSpanElement,
    type: "span",
    alternate: null,
    key: "2",
    updateQueue: null,
    memoizedState: null,
    pendingProps: {children: 0},
    memoizedProps: {children: 0},
    tag: 5,
    effectTag: 0,
    nextEffect: null
}
複製代碼

fiber節點上有不少的域,alternateeffectTag 和 nextEffect  這幾個域的用處在以前的部分已經講解過了,如今讓咱們開始研究剩下的這些。

stateNode

用於保存類組件的實例,宿主組件的 DOM 實例等。一般咱們也能夠說這個屬性是用來保存與該 fiber 相對應的的本地狀態 。

type

定義了與該 fiber node 相對應的是一個函數組件仍是一個類組件。若是是一個類組件該屬性指向這個類的構造函數。若是是一個 DOM 元素,該屬性則是與之相對應的 HTML 標籤。使用這個域很容易就能理解與該 fiber 節點相關聯的元素是什麼。

tag

定義了當前 fiber 的類型。協調算法用它來判斷具體須要作什麼工做。像以前說的這個工做的類型仍是基於 React Element 的類型。createFiberFromTypeAndProps 函數映射一個 React Element 到與之相對應的 fiber node 類型。在咱們的示例中, ClickCounter 組件的屬性 tag 是 1,表示  ClassComponent , span 元素的 tag 是5 ,表示  HostComponent

updateQueue

一個狀態更新隊列,包括回調 和 DOM 更新。

memoizedState

已經被使用渲染的過的 fiber 狀態。也就是當前屏幕上 UI  狀態的映射。

memoizedProps

已經使用渲染過的 fiber 屬性,也是構成當前屏幕 UI  狀態映射的一部分。

pendingProps

保存着最近一次從 render 方法返回的 React Element 中拿到的數據,等待隨後被應用到子組件或是 DOM 元素上。

key

相同層級孩子節點惟一標記,能夠優化提高 React 對子節點更新,添加,刪處的判斷效率。與它具體功能相關的官方文檔能夠看這裏。reactjs.org/docs/lists-…

通用算法

React 工做執行大體能夠分爲兩個階段:rendercommit

render階段,React 經過調度  setState 或者 React.render 來實現組件更新,同時也會計算出須要被更新的那部分 UI 的狀態。若是是是初始化渲染,React 會爲 render方法返回的每個 React Element 建立一個 fiber 對象。在隨後的更新中,若是當時建立 fiber時對應的 Reatc Element 還存在,那麼該 fiber還會被複用,或者更新。這個階段最後的成果就是在這顆 fiber  樹上對標記了出了那些有反作用的 fiebr 節點。在接下來的 commit 階段就是處理這些被標記節點反作用的節點 ,最後呈現爲可視化的 UI。

有一件很重要的事情須要理解,那就是 render 階段的執行能夠是異步的。React 會處理一個或多個 fiber 節點 ,基於可利用的有效時間,若是有效時間用完它就會停下來,亦或者讓位於優先級更高的事件,例如:用戶點擊操做等。隨後再次找到它暫停的位置處繼續它未完成的工做。但有時,則須要放棄已經執行過的工做,而後從頭開始。這些暫停之因此成爲多是由於它們執行的工做並不會形成用戶可見的改變,像 DOM 更新之類的。相反,接下來的  commit  階段則都是同步執行的。由於這個階段全部執行的工做是反作用應用,最主要的DOM 更新,會致使用戶可見的改變。這就是爲何 React 須要一次性執行完全部反作用的緣由。

調用生命週期方法也是反作用的一部分。一些方法在 render 階段被調用,剩下的固然在 commit 階段被調用啦。下面列舉了在 render 階段調用的生命週期方法:

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

從列表中你能夠看到,不少在 render 階段被調用的舊的生命週期方法在 version 16.3  及後面的版本中都被標記上了 UNSAFE。如今在這篇文檔中它們被統稱爲遺留生命週期方法。它們頗有可能會在將來的版本中被移除。

想知道緣由嗎?

好吧,其實這其中的緣由與咱們上面學到的知識息息相關,首先咱們知道 render 階段的更新不會有任何影響用戶視覺上可見的的反作用產生,例如: DOM 更新之類的,甚至 React 還會異步的更新組件。然而這些被使用  UNSAFE 標記的生命週期方法常常會被開發者錯誤的使用,甚至濫用。開發者傾向於將包含反作用的代碼放置到這些方法中,這樣就可能致使新的異步渲染被觸發,例如:在 componentWillUpdate函數中調用 setState 方法就會致使錯誤產生。甚至程序無限循環,直至奔潰。UNSAFE 標記也是提醒開發者慎重使用

接下來咱們講講 commit階段被調用的生命週期方法:

  • getSnapshotBeforeUpdate
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

由於這些方法的執行的執行都是同步的,因此它們能夠包含不少的反作用以及 DOM 操做。

ok,目前咱們已經掌握了足夠的背景知識,接下來就能夠潛入探究一番, React 用到的遍歷和執行相關操做的算法。

Render phase

協調算法的執行老是從頂層的 HostRoot節點開始執行工做,經過調用  renderRoot 方法開始。然而 React 會跳過那些被處理過的 fiber 節點,直到找到還未被處理的節點。例如,若是你在組件樹的某一處調用了 setStateReact 會從頂部開始遍歷,可是會快速的跳過它的祖先節點,直到找到觸發 setState 的組件爲止。

循環工做的主要步驟

全部的 fiebr節點都在在循環遍歷中被處理。咱們先看看循環算法實現的同步部分:

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

代碼中的 nextUnitOfWork 變量保存着從 workInProgress 樹中取出須要被處理的 fiebr 節點的引用。當 React 遍歷這個 Fiber樹的時候,它可使用這個變量的引用來不斷檢索遍歷到剩下還未被處理的 fiber節點。在當前的 fiber 節點被處理後,這個變量隨後會指向下一個要被處理的 fiber 節點的引用,存在的話。不然爲 null

在遍歷樹和初始化的過程當中重要用的的 4 個方法:

爲了說明他們是如何被使用的,請看下面的 fiber 樹遍歷的示意動畫:

workloop.gif

注意:垂直方向上節點之間經過 siblings 屬性鏈接,折線處表示鏈接孩子節點,經過 children 屬性。

讓咱們首先從 performUnitOfWork 和 beginWork這兩個方法開始。

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  樹的 fiber 節點做爲參數,同時調用 beginWork 方法。一個 fiber節點的全部須要執行的處理都開始於這個方法。爲了證實這一點,咱們簡單的在日誌中記錄那些已經完成功能處理的 fiber 節點的名字。 beginWork 方法老是會返回一個指向下一個將要被處理的孩子節點的指針,或者 null

若是存在下一個孩子節點,那麼它將在循環中被賦值給  nextUnitOfWork 變量。然而要是返回了 null,這時 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 就會進入這個函數。當完成當前遍歷到的孩子節點的工做後,React 就檢查是否有兄弟節點,若是有 React 會退出這個函數,並返回該節點兄弟節點的指針。它一樣會被賦值給 nextUnitOfWork 變量,節點來 React 會重複上面的過程,直至整棵子樹被遍歷處理完成。當子節點以及子節點的孩子節點都被處理完成後,回溯至父節點,再重複循環父節點的兄弟節點,直至整棵樹被遍歷完成,最終返回到根節點  fiberRoot

Commit phase

這個階段開始於  completeRoot  函數。在這個裏 React會更新 DOM ,調用相關生命週期方法。

當 React 進入這個階段,它有兩棵樹和反作用列表。第一棵樹就是是當前已經刷新到屏幕上 UI 對應的狀態。另外一顆備用樹就是在 render 階段構建的,在源碼中它一般稱之爲finishedWork 或  workInProgress ,在接下來的 commit 階段會替換以前的舊樹,將新的狀態刷新到屏幕上。

finishedWork 樹的上經過 nextEffect 指針鏈接的 fiber節點構成反作用列表。_反作用列表能夠看作是 render階段運行產生的成果。_渲染的意義就是去決定節點的插入,更新,刪除,或是組件生命週期函數的調用。這些就是反作用列表將要告訴咱們的,也是接下來提交階段須要遍歷的節點集合。

在提交階段運行的主函數是 commitRoot,基本上它作了以下工做:

  • 在標記 Snapshot  反作用的節點上調用 getSnapshotBeforeUpdate 生命週期方法。
  • 在標記了 Deletion  反作用的節點上調用 componentWillUnmount 生命週期方法。
  • 執行全部的 DOM 插入,更新,刪除操做。
  • 讓 current指針指向  finishedWork 樹。
  • 在標記了 Placement 反作用的組件節點上調用 componentDidMount 生命週期方法。
  • 在標記了 Update 反作用的組件節點上調用 componentDidUpdate 生命週期方法。

在 getSnapshotBeforeUpdate  調用後,React 會提交整棵樹的全部反作用。整個過程分爲兩步。第一步執行 DOM 插入,更新,刪除,ref 的卸載。接下來 ReactfinishedWork 賦值給 FiberRoot ,並標記 workInProgress 樹爲  current 樹。這樣作的緣由是,第一步至關因而 componentWillUnmount 階段,current指向以前的樹,而接下里的第二步則至關因而 componentDidMount/Update 階段,current要指向新樹。

上面描述的主要執行函數:

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 更新

React 執行DOM 更新使的是  commitAllHostEffects  函數。

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

很是有趣,在 commitDeletion  函數中 React 調用 componentWillUnmount  是方法做爲刪除處理的一部分。

剩餘生命週期方法

commitAllLifecycles 函數中 React 調用了全部剩下的生命週期方法,componentDidUpdate , componentDidMount

總結

譯者添加

協調過程實際就是遍歷整個 Fiber 樹的時候,經過從 React Element 中獲取到的改變後的數據,而後將這些數據更新到其所對應的 fiber 節點上,不存在對應的fiber 節點時,則建立新的,而後經過數據計算判斷出該 fiber 節點在 commit 階段須要作的事情,添加上對應的 effectTag ,同時該節點也會被添加到反作用列表中。在遍歷完成以後會生成一棵新Fiber 樹,該樹中的 fiber 節點一些是新建立的,一些則是複用 old fiber tree 中的,具體狀況取決於返回的 React Element 。在 commit 階段就是遍歷反作用列表並執行 effectTag 標記的工做。

相關文章
相關標籤/搜索