React16源碼之React Fiber架構

原文地址:github.com/HuJiaoHJ/bl…node

本文源碼是2018年8月10日拉取的React倉庫master分支上的代碼react

React源碼分析內容很是多,本文專一在如下兩個問題:git

  • JSX -> ? -> DOM
  • React Component 的生命週期函數是怎麼被調用的?

在開始源碼分析以前,首先先簡單介紹一下React的一些基礎概念github

基礎概念

React定位是一個構建用戶界面的JavaScript類庫,使用JavaScript開發UI組件,支持多種方式渲染組件,輸出用戶界面。web

React常見的三種應用類型:算法

  • React Web 應用
  • React Native 應用
  • React服務端渲染

這三種應用分別對應三種不一樣的渲染方式:數組

  • Web DOM 渲染
  • 客戶端原生 View 渲染
  • 服務端字符串渲染

下面,以 React Web應用 爲例,介紹下React三個主要組成部分:瀏覽器

  • React基礎模塊(這個模塊定義了React的基礎API及組件相關內容。對應咱們開發頁面時引入的 'react' 模塊)
  • 渲染模塊(這個模塊對於不一樣類型的應用,採用不一樣的渲染方式。對應咱們開發頁面時引入的 'react-dom' 模塊)
  • Reconciliation 模塊(又叫 協調模塊,這個模塊是上面兩個模塊的基礎,也是本文分享的重點,主要負責任務協調、生命週期函數管理等)

在開始 Reconciliation 模塊以前,先簡單介紹各個模塊:緩存

React基礎模塊

const React = {
  Children: {...},

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,

  Fragment: REACT_FRAGMENT_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
  unstable_Profiler: REACT_PROFILER_TYPE,

  createElement,
  cloneElement,
  createFactory,
  isValidElement,

  version: ReactVersion,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
複製代碼

從上面的源碼能夠看到,React基礎模塊只包括了基礎的API和組件相關的定義。如:createRef、Component等。babel

其中能夠重點關注的兩點:

一、React.creatElement

在平時的開發中,咱們使用的JSX語法,因此咱們並無直接接觸到 React.creatElement 方法

你們都知道,JSX語法會被babel編譯成調用 React.creatElement 方法,以下:

而 React.creatElement 最終返回的是 React Element,數據結構以下:

{
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
}
複製代碼

能夠在頁面中把 <App/> 打印出來,以下:

二、React.component

組件是咱們開發使用最多的,咱們能夠簡單的看下源碼:

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
複製代碼

從Component的定義上能夠看到,咱們經常使用的 setState 方法是調用了 updater.enqueueSetState,以 react-dom 爲例,此 updater 對象會調用該組件構造函數時(這塊會在後面的生命週期函數調用中講到),賦值爲classComponentUpdater,源碼以下:

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    ...
  },
  enqueueReplaceState(inst, payload, callback) {
    ...
  },
  enqueueForceUpdate(inst, callback) {
    ...
  },
};
複製代碼

能夠知道,組件中調用 setState 實際上是調用的 classComponentUpdater.enqueueSetState 方法,這裏就是開始 setState 的入口

至此,就簡單的介紹了React基礎模塊,下面開始介紹渲染模塊:react-dom

渲染模塊:react-dom

const ReactDOM: Object = {
  createPortal,
  findDOMNode(
    componentOrElement: Element | ?React$Component<any, any>,
  ): null | Element | Text {
    ...
  },
  hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
    return legacyRenderSubtreeIntoContainer(null, element, container, true, callback,);
  },

  render(element: React$Element<any>, container: DOMContainer, callback: ?Function,) {
    return legacyRenderSubtreeIntoContainer(null, element, container, false, callback,);
  },
  ...
};
複製代碼

這裏咱們能夠關注下 render 方法,全部 react web應用入口都會調用 ReactDOM.render(),本文也會從 ReactDOM.render() 開始進行源碼的分析

在進行源碼分析以前,先介紹下本文的核心:Reconciliation模塊

Reconciliation模塊

Reconciliation模塊又叫協調模塊,而咱們題目上說的 React Fiber 則是在這個模塊中使用一種調度算法

React Fiber調度算法又叫 Fiber Reconciler,是 React 16 啓用的一種新的調度算法,是對核心調度算法(Stack Reconciler)的重構

Stack Reconciler

React 16版本以前使用的 Stack Reconciler 調度算法,它經過遞歸的形式遍歷 Virtual DOM,存在難以中斷和恢復的問題,若是react更新任務運行時間過長,就會阻塞佈局、動畫等的運行,可能致使掉幀。它的調用棧以下:

Fiber Reconciler

容許渲染過程分段完成,而沒必要須一次性完成,中間能夠返回至主進程控制執行其餘任務,它有以下新特性:

  • 可拆分,可中斷任務
  • 可重用各分階段任務,且能夠設置優先級
  • 能夠在父子組件任務間前進後退切換任務
  • render方法能夠返回多元素(便可以返回數組)
  • 支持異常邊界處理異常

它的調用棧以下:

關於React新老調度算法的對比,你們能夠看看:zhuanlan.zhihu.com/p/37095662

關於React Fiber概念的再詳細的介紹,你們能夠看看:www.ayqy.net/blog/dive-i…

以上,就對React的基本概念進行了介紹,接下來開始源碼分析~

源碼分析

React Fiber架構引入了新的數據結構:Fiber節點

Fiber

Fiber節點數據結構以下:

export type Fiber = {|
  // Tag identifying the type of fiber.
  tag: TypeOfWork,
  // Unique identifier of this child.
  key: null | string,
  // The function/class/module associated with this fiber.
  type: any,
  // The local state associated with this fiber.
  stateNode: any,
  // Remaining fields belong to Fiber
  return: Fiber | null,
  // Singly Linked List Tree Structure.
  child: Fiber | null,
  sibling: Fiber | null,
  index: number,
  // The ref last used to attach this node.
  ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
  // Input is the data coming into process this fiber. Arguments. Props.
  pendingProps: any, // This type will be more specific once we overload the tag.
  memoizedProps: any, // The props used to create the output.
  // A queue of state updates and callbacks.
  updateQueue: UpdateQueue<any> | null,
  // The state used to create the output
  memoizedState: any,
  // A linked-list of contexts that this fiber depends on
  firstContextDependency: ContextDependency<mixed> | null,
  mode: TypeOfMode,
  // Effect
  effectTag: TypeOfSideEffect,
  // Singly linked list fast path to the next fiber with side-effects.
  nextEffect: Fiber | null,
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  expirationTime: ExpirationTime,
  childExpirationTime: ExpirationTime,

  alternate: Fiber | null,
  actualDuration?: number,
  actualStartTime?: number,
  selfBaseDuration?: number,
  treeBaseDuration?: number,
|};
複製代碼

Fiber樹結構圖(鏈表結構)以下:

源碼函數調用流程

咱們看張圖:

React組件渲染分爲兩個階段:reconciler、render。從圖上能夠看到:

  • reconciler階段是對Virtual DOM操做階段,對應到新的調度算法中,就是找到須要更新的工做
  • render階段是渲染階段,拿到更新工做,在不一樣應用中,使用不一樣的渲染方式進行渲染

在上面的基礎概念介紹中有提到,react-dom模塊負責react web應用的渲染工做,那麼Reconciliation模塊(協調模塊)具體作了什麼工做呢?

Reconciliation模塊的工做能夠分爲兩部分:

一、reconciliation

簡單來講就是找到須要更新的工做,經過 Diff Fiber Tree 找出要作的更新工做,這是一個js計算過程,計算結果能夠被緩存,計算過程能夠被打斷,也能夠恢復執行

因此,上面介紹 Fiber Reconciler 調度算法時,有提到新算法具備可拆分、可中斷任務的新特性,就是由於這部分的工做是一個純js計算過程,因此是能夠被緩存、被打斷和恢復的

二、commit

提交更新並調用對應渲染模塊(react-dom)進行渲染,爲了防止頁面抖動,該過程是同步且不能被打斷

下面咱們來看看這兩個階段具體的函數調用流程

reconciliation階段

咱們以 ReactDOM.render() 方法爲入口,來看看reconciliation階段的函數調用流程:

從圖中能夠看到,我把此階段分爲三部分,分別以紅線劃分。簡單的歸納下三部分的工做:

一、第一部分從 ReactDOM.render() 方法開始,把接收的React Element轉換爲Fiber節點,併爲其設置優先級,記錄update等。這部分主要是一些數據方面的準備工做。

二、第二部分主要是三個函數:scheduleWork、requestWork、performWork,即安排工做、申請工做、正式工做三部曲。React 16 新增的異步調用的功能則在這部分實現。

三、第三部分是一個大循環,遍歷全部的Fiber節點,經過Diff算法計算全部更新工做,產出 EffectList 給到commit階段使用。這部分的核心是 beginWork 函數。

第一部分

第一部分較爲簡單,這裏就不詳細介紹了,小夥伴們可自行閱讀源碼~

第二部分:任務協調

三部曲:scheduleWork、requestWork、performWork(安排工做、申請工做、正式工做)

在三部曲中的 requestWork函數中,會判斷當前任務是同步仍是異步(暫時React的異步調用功能還在開發中,未開放使用,本文後續內容是以同步任務爲例),而後經過不一樣的方式調用任務。同步任務直接調用performWork函數當即執行,而異步任務則會在後面的某一時刻被執行,那麼異步任務是怎麼被調度的呢?

異步任務調度有兩種方式,主要是經過該任務的優先級進行判斷,主要有兩種:

一、animation(動畫):則會調用 requestAnimationFrame API 告訴瀏覽器,在下一次重繪以前調用該任務來更新動畫

二、其餘異步任務:則會調用 requestIdleCallback API 告訴瀏覽器,在瀏覽器空閒時期依次調用任務,這就可讓開發者在主事件循環中執行後臺或低優先級的任務,並且不會對像動畫和用戶交互等關鍵的事件產生影響

以上兩個API都是原生API,想深刻了解的能夠看看:requestAnimationFramerequestIdleCallback

而原生requestIdleCallback存在兼容性問題,因此React自己開發了 ReactScheduler模塊 來實現這個功能

後續會以同步任務爲例,因此咱們開始介紹第三部分的核心函數:beginWork

第三部分:beginWork

從上面的函數調用流程圖能夠看到,beginWork在大循環中被調用,返回當前節點的子節點。

function beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime, ): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;
  if (!hasLegacyContextChanged() && (updateExpirationTime === NoWork || updateExpirationTime > renderExpirationTime)) {
    switch (workInProgress.tag) {
      case HostRoot:
        ...
      case HostComponent:
       ...
      case ClassComponent:
        pushLegacyContextProvider(workInProgress);
        break;
      case HostPortal:
        ...
      case ContextProvider:
        ...
      case Profiler:
        ...
    }
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderExpirationTime,);
  }

  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      return mountIndeterminateComponent(current, workInProgress, renderExpirationTime,);
    case FunctionalComponent:
      return updateFunctionalComponent(current, workInProgress, renderExpirationTime,);
    case ClassComponent:
      return updateClassComponent(current, workInProgress, renderExpirationTime,);
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case PlaceholderComponent:
      return updatePlaceholderComponent(current, workInProgress, renderExpirationTime,);
    case HostPortal:
      return updatePortalComponent(current, workInProgress, renderExpirationTime,);
    case ForwardRef:
      return updateForwardRef(current, workInProgress, renderExpirationTime);
    case Fragment:
      return updateFragment(current, workInProgress, renderExpirationTime);
    case Mode:
      return updateMode(current, workInProgress, renderExpirationTime);
    case Profiler:
      return updateProfiler(current, workInProgress, renderExpirationTime);
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderExpirationTime,);
    case ContextConsumer:
      return updateContextConsumer(current, workInProgress, renderExpirationTime,);
    default:
      ...
  }
}
複製代碼

首先,先介紹一下React Fiber架構的雙緩衝技術:

從上圖能夠看到有兩顆 Fiber Tree:current、workInProgress,它們之間是經過每一個Fiber節點上的alternate屬性聯繫在一塊兒,能夠查看源碼ReactFiber.js中的 createWorkInProgress 方法,以下:

export function createWorkInProgress( current: Fiber, pendingProps: any, expirationTime: ExpirationTime, ): Fiber {
  let workInProgress = current.alternate;
  if (workInProgress === null) {
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode,);
    ...
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
    workInProgress.effectTag = NoEffect;
    workInProgress.nextEffect = null;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;
    ...
  }
  ...
  return workInProgress;
}
複製代碼

以上代碼爲簡化以後的,能夠發現,current與workInProgress互相持有引用。而從上圖能夠發現,全部更新都是在workInProgress上進行操做,等更新完畢以後,再把current指針指向workInProgress,從而丟棄舊的Fiber Tree

從beginWork源碼來看,主要分爲兩部分,一部分是對Context的處理,一部分是根據fiber對象的tag類型,調用對應的update方法。在這裏咱們重點關注第二部分。而在第二部分中,咱們以 ClassComponent類型 爲例,講講 updateClassComponent函數 中作了什麼呢?

主要有兩部分:生命週期函數的調用及Diff算法

生命週期函數調用

流程圖以下:

current爲null,意味着當前的update是組件第一次渲染

一、調用 constructClassInstance 構造組件實例,主要是調用 constructor 構造函數,並注入classComponentUpdater(這塊就是文章一開始介紹React Component時提到的updater注入)

二、mountClassInstance 則是調用 getDerivedStateFromProps 生命週期函數(v16) 及 UNSAFE_componentWillMount 生命週期函數

current不爲null,調用 updateClassInstance 方法

一、若是新老props不一致,則會調用 UNSAFE_componentWillReceiveProps 生命週期函數

二、而後調用 shouldComponentUpdate 生命週期函數,得到shouldUpdate值,若未定義今生命週期函數,默認爲true(是否從新渲染),若是shouldUpdate爲true,則會調用 UNSAFE_componentWillUpdate 生命週期函數

最後調用 finishClassComponent 方法,那麼 finishClassComponent函數 中作了什麼呢?流程圖以下:

若是 shouldUpdate 爲false,表示不須要更新,直接返回

若是 shouldUpdate 爲true,調用實例的 render 方法,返回新子節點

若是是首次渲染,調用 mountChildFibers 建立子節點的Fiber實例

不然,調用 reconcileChildFibers 對新老子節點進行Diff

執行到了這,updateClassComponent函數主要是執行了組件的生命週期函數,下面講講須要對新老子節點進行Diff時使用的Diff算法

Diff算法

reconcileChildFibers函數 中,源碼以下:

function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, expirationTime: ExpirationTime, ): Fiber | null {
    const isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;
    if (isUnkeyedTopLevelFragment) {
        newChild = newChild.props.children;
    }
    const isObject = typeof newChild === 'object' && newChild !== null;
    if (isObject) {
        switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
            return placeSingleChild(
                reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime,),
            );
        case REACT_PORTAL_TYPE:
            return placeSingleChild(
                reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime,),
            );
        }
    }
    if (typeof newChild === 'string' || typeof newChild === 'number') {
        return placeSingleChild(
            reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, expirationTime,),
        );
    }
    if (isArray(newChild)) {
        return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, expirationTime,);
    }
    if (getIteratorFn(newChild)) {
        return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, expirationTime,);
    }
    if (isObject) {
        throwOnInvalidObjectType(returnFiber, newChild);
    }
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
}
複製代碼

reconcileChildFibers函數中主要是根據newChild類型,調用不一樣的Diff算法:

一、單個元素,調用reconcileSingleElement

二、單個Portal元素,調用reconcileSinglePortal

三、string或者number,調用reconcileSingleTextNode

四、array(React 16 新特性),調用reconcileChildrenArray

前三種狀況,在新子節點上添加 effectTag:Placement,標記爲更新操做,而這些操做的標記,將用於commit階段。下面以單個元素爲例,講講具體的Diff算法

reconcileSingleElement函數源碼以下:

function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement, expirationTime: ExpirationTime, ): Fiber {
    const key = element.key;
    let child = currentFirstChild;
    while (child !== null) {
        // 判斷key是否相等
        if (child.key === key) {
            if (child.tag === Fragment ? element.type === REACT_FRAGMENT_TYPE : child.type === element.type) {
                // key相等且type相等,刪除舊子節點的兄弟節點,複用舊節點並返回
                deleteRemainingChildren(returnFiber, child.sibling);
                const existing = useFiber(child, element.type === REACT_FRAGMENT_TYPE ? element.props.children : element.props, expirationTime,);
                existing.ref = coerceRef(returnFiber, child, element);
                existing.return = returnFiber;
                return existing;
            } else {
                // key相等但type不相等,刪除舊子節點及兄弟節點,跳出循環
                deleteRemainingChildren(returnFiber, child);
                break;
            }
        } else {
            // key不相等,刪除此舊子節點,繼續循環
            deleteChild(returnFiber, child);
        }
        // 繼續遍歷此舊子節點的兄弟節點
        child = child.sibling;
    }
    // 不能複用,則直接新建Fiber實例,並返回
    if (element.type === REACT_FRAGMENT_TYPE) {
        const created = createFiberFromFragment(element.props.children, returnFiber.mode, expirationTime,
        element.key,);
        created.return = returnFiber;
        return created;
    } else {
        const created = createFiberFromElement(element, returnFiber.mode, expirationTime,);
        created.ref = coerceRef(returnFiber, currentFirstChild, element);
        created.return = returnFiber;
        return created;
    }
}
複製代碼

具體過程在代碼的註釋中寫的比較清楚,在這就不詳細展開。不過咱們能夠看看 deleteChild(刪除子節點)中,具體作了什麼,源碼以下:

function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
    if (!shouldTrackSideEffects) {
        return;
    }
    const last = returnFiber.lastEffect;
    if (last !== null) {
        last.nextEffect = childToDelete;
        returnFiber.lastEffect = childToDelete;
    } else {
        returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
    }
    childToDelete.nextEffect = null;
    childToDelete.effectTag = Deletion;
}
複製代碼

能夠看到,deleteChild 刪除子節點並非真的刪除這個對象,而是經過 firstEffect、lastEffect、nextEffect 屬性來維護一個 EffectList(鏈表結構),經過 effectTag 標記當前刪除操做,這些信息都會在 commit 階段使用到

以上,就是beginWork函數的整個過程,能夠知道遍歷完Fiber樹以後,經過Diff算法,能夠產出 EffectList,給commit階段使用

commit階段

函數調用流程圖以下:

commit階段作的事情是拿到reconciliation階段產出的EffectList,即全部更新工做,提交這些更新工做並調用渲染模塊(react-dom)渲染UI。

effectTag

在前面也提到,commit階段會經過 effectTag標記 識別操做類型,因此咱們先來看看 effectTag 有哪些類型:

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b00000000000;
export const PerformedWork = /* */ 0b00000000001;
// You can change the rest (and add more).
export const Placement = /* */ 0b00000000010;
export const Update = /* */ 0b00000000100;
export const PlacementAndUpdate = /* */ 0b00000000110;
export const Deletion = /* */ 0b00000001000;
export const ContentReset = /* */ 0b00000010000;
export const Callback = /* */ 0b00000100000;
export const DidCapture = /* */ 0b00001000000;
export const Ref = /* */ 0b00010000000;
export const Snapshot = /* */ 0b00100000000;
// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /* */ 0b00110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b00111111111;
export const Incomplete = /* */ 0b01000000000;
export const ShouldCapture = /* */ 0b10000000000;
複製代碼

能夠看到:

一、effectTag類型是使用二進制位表示,能夠多個疊加

二、經過位運算匹配effectTag類型

從上面的流程圖,能夠看到commit階段有比較重要的三個函數:

一、commitBeforeMutationLifecycles

此函數主要是保存當前DOM的一個快照,執行 getSnapshotBeforeUpdate 生命週期函數

二、commitAllHostEffects

提交全部更新並渲染,源碼以下:

function commitAllHostEffects() {
  while (nextEffect !== null) {
    recordEffect();
    const effectTag = nextEffect.effectTag;
    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }
    let primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement: {
        commitPlacement(nextEffect);
        nextEffect.effectTag &= ~Placement;
        break;
      }
      case PlacementAndUpdate: {
        commitPlacement(nextEffect);
        nextEffect.effectTag &= ~Placement;
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion: {
        commitDeletion(nextEffect);
        break;
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}
複製代碼

從源碼能夠看到,此函數主要是遍歷EffectList,根據effectTag,調用對應commit方法,進而調用react-dom提供的操做DOM的方法,渲染UI,操做DOM的方法有:

{
  getPublicInstance,
  supportsMutation,
  supportsPersistence,
  commitMount,
  commitUpdate,
  resetTextContent,
  commitTextUpdate,
  appendChild,
  appendChildToContainer,
  insertBefore,
  insertInContainerBefore,
  removeChild,
  removeChildFromContainer,
  replaceContainerChildren,
  createContainerChildSet,
}
複製代碼

注意,在調用刪除操做的commit方法時,會執行 componentWillUnmount 生命週期函數

在這個方法中,基本完成了將更新提交併渲染UI的工做

三、commitAllLifeCycles

此函數主要是根據fiber節點類型,執行相應的處理,以 ClassComponent 爲例,完成UI渲染以後,會執行後續的生命週期函數:

一、判斷是否首次渲染,是則執行 componentDidMount 生命週期函數

二、不然,執行 componentDidUpdate 生命週期函數

以上就是commit階段的全過程

至此,咱們源碼等的全過程也完成了,咱們再總結一下整個函數調用流程:

總結

最後,咱們回到一開始的那兩個問題:

  • JSX -> ? -> DOM
  • React Component 的生命週期函數是怎麼被調用的?

如今,是否是以爲整個過程都很清晰了呢~~~

附上,生命週期函數彙總表:

寫在最後

以上就是我對React16源碼的分享,但願能對有須要的小夥伴有幫助~~~

喜歡個人文章小夥伴能夠去 個人我的博客 點star ⭐️

相關文章
相關標籤/搜索