深刻react的state和props更新

本文爲意譯和整理,若有誤導,請放棄閱讀。原文html

前言

這篇文章用一個由parent component和children component組成的例子來說述fiber架構中react將props傳遞給子組件的處理流程。node

正文

在我先前的文章中 Inside Fiber: in-depth overview of the new reconciliation algorithm in React 提到要想理解更新流程的技術細節,咱們需得具有必定的基礎知識。而這部分的基礎知識就是篇文章要講述的內容。react

對於本文所提到的數據結構和概念,我已經在上一篇文章概述過了。這些數據結構和概念主要包括有:git

  • fiber node
  • current tree
  • work-in-progress tree
  • side-effects
  • effects list

同時,我也對主要算法進行了宏觀上的闡述,也解釋過render階段和commit階段之間的差別性。若是你尚未閱讀過講述這些東西的文章,我建議你先去閱讀。github

我也引入過一個簡單demo。這個demo的主要功能是經過點擊button來增長界面上的一個數字。 算法

你能夠這裏去玩玩它。這個demo實現了一個簡單的組件。這個組件的render方法返回了兩個子組件:button和span。當你點擊界面上的按鈕的時候,咱們會在click的事件處理器中去更新組件的state。結果是,界面上span元素的文本內容獲得更新。數組

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};
        });
    }
    
    componentDidUpdate() {}

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

在這裏,我把一個componentDidUpdate的生命週期函數加入到組件中。這麼作,是爲了演示React是如何添加effects和在commit階段調用這個方法的。瀏覽器

在本文中,我會帶你看看,React是如何處理state更新和構建effects list的。咱們也對rendercommit階段的頂層函數進行簡單的講解。安全

特別地,咱們着重看看completeWork方法:bash

  • 更新ClickCounter組件state中的count屬性。
  • 調用組件實例的render方法,獲取到children列表,而後執行比對。
  • 更新span元素的props。

commitRoot方法:

  • 更新span元素的textContent屬性。
  • 調用componentDidUpdate這個生命週期函數。

在深刻這些東西以前,咱們快速地過一遍「當咱們在click事件處理器中調用setState的時候,work是如何被調度」的這一環節。

Scheduling updates

當咱們點擊界面上的button的時候,click事件被觸發了,而後React執行咱們做爲props傳遞進去的事件回調。在咱們的demo中,這個事件回調就是簡單地經過增長count字段值來更新組件的狀態。

class ClickCounter extends React.Component {
    ...
    handleClick() {
        this.setState((state) => {
            return {count: state.count + 1};
        });
    }
}   
複製代碼

每個React組件都有本身的updater,這個updater充當着組件與React core通信的橋樑。這種設計,使得多個render(好比:ReactDOM, React Native, server side rendering和testing utilities)去實現本身的setState方法成了可能。

在這篇文章中,咱們單獨分析一下updater對象在ReactDOM中的實現。在這個實現中,就用到了Fiber reconciler。具體對於ClickCounter組件來講,這個updater對象就是classComponentUpdater。它的職責有:1)把Fiber的實例檢索回來; 2)將更新請求入隊;3)對work進行調度。

當咱們說「一個更新請求被入隊」,其實意思就是把一個setState的callback添加到Fiber node的「updateQueue」隊列中去,等待處理。迴歸到本示例,ClickCounter組件所對應的Fiber node具體的數據結構:

{
    stateNode: new ClickCounter,
    type: ClickCounter,
    updateQueue: {
         baseState: {count: 0}
         firstUpdate: {
             next: {
                 payload: (state) => { return {count: state.count + 1} }
             }
         },
         ...
     },
     ...
}
複製代碼

正如你所看到的那樣,updateQueue.firstUpdate.next.payload引用所指向的那個函數就是咱們傳給setState方法的那個callback。它表明着render階段第一個須要被處理的「更新請求」。

處理ClickCounter Fiber node身上的更新請求

在我先前的那篇文章關於work loop的那一章節中,我已經解釋過nextUnitOfWork這個全局變量所扮演的角色了。特別地,這一章節說到了這個全局變量指向的是workInProgresstree上那些有work須要去作的Fiber node。當React遍歷整顆Fiber樹的時候,就是用這個全局變量來判斷是否還有未完成本身的work的Fiber node。

咱們從setState方法已經被調用的地方開始提及。在setState方法被調用以後,React會把咱們傳給setState的callback傳遞ClickCounterfiber node,也就是說把這個callback添加到fiber node的updateQueue對象中。而後,就開始調度work。也是從這裏開始,React開始進入了render階段了。它調用renderRoot這個函數,從最頂層的HostRoot開始遍歷整顆fiber node樹。儘管是從最頂層的根節點開始,可是React會掉過那些已經處理過的 fiber node,只會處理那些還有work須要去完成的節點。此時此刻,咱們只有一個fiber node是有work須要去作的。這個node就是ClickCounterfiber node。

ClickCounterfiber node的alternate字段用於保存一個指向[當前fiber node的克隆副本]的引用。這個克隆副本上的work都是已經執行完成的了。這個克隆副本被稱爲當前fiber node的alternate fiber node。若是alternate fiber node尚未被建立的話,那麼React就會在處理更新請求以前使用createWorkInProgress函數去完成複製工做。如今,咱們假設變量nextUnitOfWork保存着指向當前fiber node的alternate fiber node的引用。

beginWork

首先,咱們的fiber node將會被傳遞到beginWork 函數裏面。

由於這個函數會在fiber node tree上的每個節點調用。因此,若是你想調試render階段,這是一個打斷點的好地方。我常常這麼幹,經過檢測fiber node的type值來肯定當前節點是不是我要跟進的那個。

beginWork函數基本上就是一個大的switch語句。在這個switch語句,beginWork根據workInProgress的tag值來計算初當前fiber node所須要完成的work的類型。而後,執行相應的函數去執行這個work。在咱們的demo中,由於ClickCounter是一個class component,因此,咱們會執行如下的分支語句:

function beginWork(current$$1, workInProgress, ...) {
    ...
    switch (workInProgress.tag) {
        ...
        case FunctionalComponent: {...}
        case ClassComponent:
        {
            ...
            return updateClassComponent(current$$1, workInProgress, ...);
        }
        case HostComponent: {...}
        case ...
}
複製代碼

那麼,咱們會進入updateClassComponent函數中。取決於當前:1)是不是組件的首次渲染:2)是不是work正在被恢復執行;3)是不是一次React更新,React會幹兩件事情:

  • 要麼建立一個新實例,並掛載這個組件;
  • 要麼僅僅是更新它。
function updateClassComponent(current, workInProgress, Component, ...) {
    ...
    const instance = workInProgress.stateNode;
    let shouldUpdate;
    if (instance === null) {
        ...
        // In the initial pass we might need to construct the instance.
        constructClassInstance(workInProgress, Component, ...);
        mountClassInstance(workInProgress, Component, ...);
        shouldUpdate = true;
    } else if (current === null) {
        // In a resume, we will already have an instance we can reuse.
        shouldUpdate = resumeMountClassInstance(workInProgress, Component, ...);
    } else {
        shouldUpdate = updateClassInstance(current, workInProgress, ...);
    }
    return finishClassComponent(current, workInProgress, Component, shouldUpdate, ...);
}
複製代碼

Processing updates for the ClickCounter Fiber

咱們已經爲ClickCounter建立過一個實例了,因此,咱們的執行將會進入updateClassInstance方法。在這個方法中,React執行了class component絕大部分的work。如下是這個方法執行的最重要的操做(羅列的順序也是代碼執行的順序):

  • 調用UNSAFE_componentWillReceiveProps生命週期函數(已棄用);
  • 處理updateQueue中的更新請求和生成一個新的state值;
  • 用一個新的state值去調用getDerivedStateFromProps,並獲取調用結果。
  • 調用shouldComponentUpdate來確保一個組件是否真的想要更新。若是調用返回值爲false的話,那麼React將會跳過整個渲染流程包括調用組件實例和它的子組件實例的render方法。不然的話,正常走更新流程。
  • 調用UNSAFE_componentWillUpdate生命週期函數(已棄用);
  • 把生命週期函數componentDidUpdate添加成一個effect。

雖然,「調用componentDidUpdate」這個effect是在render階段添加的,可是這個方法的實際執行是在接下來的commit階段。

  • 更新組件實例上的state和props值。

state和props值的更新應該是在render方法調用前的。由於render的返回值是須要依賴最新的state和props值(譯者注:這也是指出了一個事實,即react組件更新的本質就是用最新的state和props值去調用組件實例的render方法)。若是咱們不這麼幹的話,那麼render方法的每一次調用的返回值都是同樣的。

下面是updateClassInstance方法的精簡版:

function updateClassInstance(current, workInProgress, ctor, newProps, ...) {
    const instance = workInProgress.stateNode;

    const oldProps = workInProgress.memoizedProps;
    instance.props = oldProps;
    if (oldProps !== newProps) {
        callComponentWillReceiveProps(workInProgress, instance, newProps, ...);
    }

    let updateQueue = workInProgress.updateQueue;
    if (updateQueue !== null) {
        processUpdateQueue(workInProgress, updateQueue, ...);
        newState = workInProgress.memoizedState;
    }

    applyDerivedStateFromProps(workInProgress, ...);
    newState = workInProgress.memoizedState;

    const shouldUpdate = checkShouldComponentUpdate(workInProgress, ctor, ...);
    if (shouldUpdate) {
        instance.componentWillUpdate(newProps, newState, nextContext);
        workInProgress.effectTag |= Update;
        workInProgress.effectTag |= Snapshot;
    }

    instance.props = newProps;
    instance.state = newState;

    return shouldUpdate;
}

複製代碼

我已經把一些比較次要的代碼移除掉了。舉個例子,在調用生命週期函數和添加effect並觸發它以前,React會用typeof操做符去檢查這個組件是否實現了某個方法。下面的代碼中,React會在添加effect以前檢查componentDidUpdate方法是不是一個function:

if (typeof instance.componentDidUpdate === 'function') {
    workInProgress.effectTag |= Update;
}
複製代碼

到了這裏,咱們已經知道在render階段,ClickCounter fiber node須要執行哪些操做了。下面,咱們來看看,這些操做是如何改變fiber node上的相關值的。當React開始執行work的時候,ClickCounter組件所對應的fiber node長這樣的:

{
    effectTag: 0,
    elementType: class ClickCounter,
    firstEffect: null,
    memoizedState: {count: 0},
    type: class ClickCounter,
    stateNode: {
        state: {count: 0}
    },
    updateQueue: {
        baseState: {count: 0},
        firstUpdate: {
            next: {
                payload: (state, props) => {…}
            }
        },
        ...
    }
}
複製代碼

當work執行完畢,ClickCounter組件所對應的fiber node已經長成這樣的:

{
    effectTag: 4,
    elementType: class ClickCounter,
    firstEffect: null,
    memoizedState: {count: 1},
    type: class ClickCounter,
    stateNode: {
        state: {count: 1}
    },
    updateQueue: {
        baseState: {count: 1},
        firstUpdate: null,
        ...
    }
}
複製代碼

仔細觀察一下連個fiber node屬性值之間的差別。咱們會發現,在處理完更新請求後,memoizedState和baseState中的count字段的屬性值已經變爲1了。與此同時,React也把ClickCounter的組件實例的狀態也更新了。

當前,咱們在updateQueue中已經沒有更新請求了,因此firstUpdate的值爲null。還有很重要的一點,咱們的effectTag字段的值已經從0變爲4了。4用二進制表示就是100,而這就是update這個side-effect的tag值

export const Update = 0b00000000100;
複製代碼

下面作個小總結。當React在ClickCounterfiber node上執行work的時候,React要作的事有:

  • 調用pre-mutation生命週期方法
  • 更新state值
  • 定義相關的side-effect(譯者注:將某些操做標記爲side-effect)

Reconciling children for the ClickCounter Fiber

當上面提到的小總結的東西完成後,React執行將會進入finishClassComponent。在這個函數裏面,React將會調用組件實例的render方法,而後在它的子組件實例(正是render方法返回的東西)上應用diff算法。在這篇文章裏面有一個關於diff算法高質量的歸納:

當對比中的兩個react DOM element(譯者注:本質上就是react element,可是type的值是DOM類型的字符串)具體相同的type的時候,React會查看二者的attribute的差別性,保留底層所對應的DOM node對象,只是更新那些須要改變的attribute。

若是咱們再深究一點的話,那麼,咱們會了解到其實對比是react element所對應的fiber node。在本文中,我不會討論太多細節,由於這裏面的處理流程仍是挺複雜的。我將會在一個單獨的文章上專門來說述child reconciliation的處理流程。

若是你着急去了解child reconciliation細節的話,那麼你能夠查看這個reconcileChildrenArray函數。由於在咱們這個demo中,ClickCounter的render方法返回的是一個react element組成的數組。

當前,有兩件重要的事情須要咱們去理解。第一件是,隨着child reconciliation流程的執行,React會爲從render方法中返回的child react element建立或者更新對應的fiber node。finishClassComponent函數會返回當前fiber node第一個child fiber node的引用。這個引用將會賦值給nextUnitOfWork,而且會在work loop的下一個循環中使用到;第二件事是,React把對子fiber node 的props的更新看成父fiber node的work的一部分。爲了達成這事,React會使用從render方法返回的react element身上的數據。

舉個例子,在React對ClickCounterfiber node 的children進行reconcile以前,span元素所對應的fiber node是長這樣的:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    key: "2",
    memoizedProps: {children: 0},
    pendingProps: {children: 0},
    ...
}

複製代碼

正如你所見的那樣,memoizedPropspendingProps中的children屬性值都是0 。而下面,就是調用render方法後返回的span元素所對應的react element:

{
    $$typeof: Symbol(react.element)
    key: "2"
    props: {children: 1}
    ref: null
    type: "span"
}
複製代碼

正如你所見的那樣,fiber node中的props與返回的react element中的props是不一樣的。在createWorkInProgress函數中,這種不一樣性會應用 在alternate fiber node的建立過程當中。React就是從react element上拷貝已經更新的props到alternate fiber node上的。

當React對ClickCounter組件的children完成了reconcile以後,span元素所對應的fiber node的pendingProps字段的值將獲得更新。該字段值將會跟span元素所對應的react element的props值保持一致:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    key: "2",
    memoizedProps: {children: 0},
    pendingProps: {children: 1},
    ...
}
複製代碼

稍後,React會span元素所對應的fiber node執行work,它會將它們複製到memoizedProps上,並向DOM更新上添加effect(add effect to DOM update)。

到此爲止,咱們已經講完了ClickCounterfiber node在render階段所須要執行的全部的work了。由於button組件是ClickCounter組件的第一個子元素,因此,它所對應的fiber node將會被賦值給nextUnitOfWork變量。由於這個fiber node沒有任何work須要去作的。因此,React會移步到它的sibling-span元素所對應的fiber node。根據這裏所描述的算法能夠得知,以上過程發生在completeUnitOfWork函數裏面。

Processing updates for the Span fiber

因此,nextUnitOfWork變量如今指向span元素所對應的fiber node(後面簡稱爲「span fiber node」)的alternate fiber node。React對span fiber node的更新處理流程就是從這裏開始。跟ClickCounter fiber node的處理流程是同樣的,咱們都是從beginWork函數開始。

由於span節點屬於HostComponent類型的,因此,這一次,咱們會進入HostComponent的分支:

function beginWork(current$$1, workInProgress, ...) {
    ...
    switch (workInProgress.tag) {
        case FunctionalComponent: {...}
        case ClassComponent: {...}
        case HostComponent:
          return updateHostComponent(current, workInProgress, ...);
        case ...
}
複製代碼

最終,咱們會進入updateHostComponent這個函數。往上,你能夠看到咱們上面在分析ClickCounter fiber node時候的所提到的updateClassComponent,針對functional component,React會執行updateFunctionComponent等等。你能夠在ReactFiberBeginWork.js文件中找到全部的這些函數的實現代碼。

Reconciling children for the span fiber

在咱們的demo中,由於span節點的子節點太過簡單了,因此在updateHostComponent函數中,沒啥過重要的事情發生。

Completing work for the Span Fiber node

一旦beginWork執行完畢,當前fiber node就會被傳遞到completeWork中去。在本示例中,這個fiber node就是span fiber node。在此以前,React須要更新span fiber node上的memoizedProps字段值。你可能還記得,當React對ClickCounter組件的子組件進行reconcile的時候,它已經更新span fiber node上的pendingProps字段:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    key: "2",
    memoizedProps: {children: 0},
    pendingProps: {children: 1},
    ...
}
複製代碼

因此,一旦beginWork函數在span fiber node上調用完畢的話,那麼React會更新memoizedProps字段值,使得它與pendingProps字段值保持一致:

function performUnitOfWork(workInProgress) {
    ...
    next = beginWork(current$$1, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
    ...
}
複製代碼

執行完beginWork函數後,React就會執行completeWork函數。這個函數的實現基本上就是一個大大的switch語句。這跟以前所提到的beginWork裏面的switch語句差很少:

function completeWork(current, workInProgress, ...) {
    ...
    switch (workInProgress.tag) {
        case FunctionComponent: {...}
        case ClassComponent: {...}
        case HostComponent: {
            ...
            updateHostComponent(current, workInProgress, ...);
        }
        case ...
    }
}
複製代碼

由於咱們的span fiber node(所對應的react element)是HostComponent,因此,咱們會進入到updateHostComponent函數裏面。在這個函數裏面,React基本上就作了如下的三件事情:

  • 爲DOM更新作準備
  • 將準備的結果添加到span fiber node的updateQueue字段中;
  • adds the effect to update the DOM

在執行這行操做以前,span fiber node長這樣的:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    effectTag: 0
    updateQueue: null
    ...
}
複製代碼

當上面的work執行完成後,span fiber node長這樣:

{
    stateNode: new HTMLSpanElement,
    type: "span",
    effectTag: 4,
    updateQueue: ["children", "1"],
    ...
}
複製代碼

請注意二者在effectTag和updateQueue字段值上的不一樣。對於effectTag的值來講,它再也不是0,而是4。用二進制表示就是100,而第三位就是update這 種side-effect所對應的二進制位。現在該位置爲1,則說明span fiber node後面所須要執行的side-effect就是update。在接下來的commit階段,對於span fiber node來講,這也是React惟一須要幫它完成的任務了。而updateQueue字段值保存的是用於update的數據。

一旦React處理完ClickCounterfiber node和它的子fiber node們,那麼render階段算是結束了。React會把產出的alternate fiber node樹賦值給FiberRoot對象的finishedWork屬性。這顆新的alternate fiber node樹包含了須要被flush到屏幕的東西。它會在render階段以後立刻被處理或者稍後在瀏覽器分配給React的,空閒的時間裏面執行。

effects list

在咱們給出的示例中,由於span fiber node和ClickCounter fiber node是有side effect的。React將會給span fiber node添加一個link,讓它指向HostFiber的firstEffect屬性

在函數compliteUnitWork中,react完成了effect list的構建。下面就是本示例中,帶有effect的fiber node樹。在這棵樹上,有着兩個effect:1)更新span節點的文本內容;2)調用ClickCounter組件的生命週期函數:

而下面是由具備effect的fiber node組成的線性列表:

commit階段

這個階段以completeRoot函數開始。在繼續往下走以前,它首先將FiberRoot的finishedWork屬性值置爲null:

root.finishedWork = null;
複製代碼

不像render階段,commit階段是同步執行的。因此,它能很安全地更新HostRoot,以此來指示commit工做已經開始了。

commit階段是React進行DOM操做和調用post-mutation生命週期方法componentDidUpate的地方。爲了實現上面這些目標,React會遍歷render階段所產出的effect list,並應用相應的effect。

就本示例而言,咱們在render階段事後,咱們有如下幾個effect:

{ type: ClickCounter, effectTag: 5 }
{ type: 'span', effectTag: 4 }
複製代碼

ClickCounterfiber node的effect tag爲5,用二進制表示就是「101」。它對應的work是update。而對於class component而言,這個work會被「翻譯爲」componentDidUpdate這個生命週期方法。在二進制「101」中,最低位爲「1」,表明着當前這個fiber node的全部work都在render階段執行完畢了。

span fiber node的effect tag值是4,用二進制表示是「100」。這個編號所表明的work是「update」,由於當前的span fiber node對應的是host component類型的。這個「update」work更具體點來講就是「DOM更新」。迴歸到本示例,「DOM更新」更具體點是指「更新span元素的textContent屬性」。

Applying effects

讓咱們一塊兒來看看,React是如何應用這些effect的。函數commitRoot就是用來應用effect的。它由三個子函數組成:

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

這三個子函數都實現對effect list的遍歷,而且在遍歷過程當中去檢查effect的類型。若是它們發現當前的這個effect跟它們函數的職責相關的,那麼就會應用這個effect。在咱們的示例中,具體點講就是在ClickCounter組件上調用componentDidUpdate這個生命週期方法和更新span元素的文本內容。

第一個子函數commitBeforeMutationLifeCycles 會查找snapshot類型的effect,並調用getSnapshotBeforeUpdate方法。由於在ClickCouner組件身上,咱們並無實現這個方法,因此React並無在render階段把這個effect添加到該組件對應的fiber node身上。因此,在咱們這個示例中,這個子函數啥事都沒作。

DOM updates

接下來,React會移步到commitAllHostEffects函數上面來。就是在這個函數裏面,React完成了將span元素的文本內容從「0」更新到「1」。這個函數幾乎跟ClickCounter這個fiber node沒有關係。由於這個fiber node對應的是class component,而class componnet是沒有任何的直接的DOM更新方面的需求的。

這個函數的大致框架是對不一樣類型的effect執行不一樣操做。在咱們這個示例中,咱們須要更新span元素的文本內容,因此咱們是要走Update這條分支的:

function updateHostEffects() {
    switch (primaryEffectTag) {
      case Placement: {...}
      case PlacementAndUpdate: {...}
      case Update:
        {
          var current = nextEffect.alternate;
          commitWork(current, nextEffect);
          break;
        }
      case Deletion: {...}
    }
}
複製代碼

順着commitWork一路走下去,咱們最終會進入updateDOMProperties函數裏面。在這個函數裏面,它使用了咱們在render階段添加到fiber node的updateQueue字段身上的payload來更新span元素的textContent屬性值:

function updateDOMProperties(domElement, updatePayload, ...) {
  for (let i = 0; i < updatePayload.length; i += 2) {
    const propKey = updatePayload[i];
    const propValue = updatePayload[i + 1];
    if (propKey === STYLE) { ...} 
    else if (propKey === DANGEROUSLY_SET_INNER_HTML) {...} 
    else if (propKey === CHILDREN) {
      setTextContent(domElement, propValue);
    } else {...}
  }
}
複製代碼

在[DOM更新]這個effect被應用後,React將finishedWork樹賦值給HostRoot。它把alternate tree設置爲current tree:

root.current = finishedWork;
複製代碼

Calling post mutation lifecycle hooks

咱們剩下最後一個commitAllLifecycles要講了。在這個函數裏面,React調用了全部的post-mutational 生命週期方法。在render階段,React往ClickCounter組件身上添加了一個叫「update」的effect。這個effect就是本函數所要查找的effect,一旦找到以後,React就會調用componentDidUpdate方法:

function commitAllLifeCycles(finishedRoot, ...) {
    while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;

        if (effectTag & (Update | Callback)) {
            const current = nextEffect.alternate;
            commitLifeCycles(finishedRoot, current, nextEffect, ...);
        }
        
        if (effectTag & Ref) {
            commitAttachRef(nextEffect);
        }
        
        nextEffect = nextEffect.nextEffect;
    }
}
複製代碼

這個函數也會更新refs,可是由於咱們這個示例中並無使用到這個特性。因此相應的那部分代碼(指commitAttachRef(nextEffect);)就不會被執行。對componentDidUpdate方法的調用是發生在commitLifeCycles函數裏面:

function commitLifeCycles(finishedRoot, current, ...) {
  ...
  switch (finishedWork.tag) {
    case FunctionComponent: {...}
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.effectTag & Update) {
        if (current === null) {
          instance.componentDidMount();
        } else {
          ...
          instance.componentDidUpdate(prevProps, prevState, ...);
        }
      }
    }
    case HostComponent: {...}
    case ...
}
複製代碼

順便你也看到,這也是React調用componentDidMount這個生命週期方法的地方。不過這個調用時機是在組件的首次掛載的過程當中而已。

相關文章
相關標籤/搜索