欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React commit源碼分析詳解

 更新時(shí)間:2022年11月04日 17:07:49   作者:goClient1992  
前兩章講到了,react 在 render 階段的 completeUnitWork 執(zhí)行完畢后,就執(zhí)行 commitRoot 進(jìn)入到了 commit 階段,本章將講解 commit 階段執(zhí)行過程源碼

總覽

commit 階段相比于 render 階段要簡(jiǎn)單很多,因?yàn)榇蟛糠指碌那捌诓僮鞫荚?render 階段做好了,commit 階段主要做的是根據(jù)之前生成的 effectList,對(duì)相應(yīng)的真實(shí) dom 進(jìn)行更新和渲染,這個(gè)階段是不可中斷的。

commit 階段大致可以分為以下幾個(gè)過程:

  • 獲取 effectList 鏈表,如果 root 上有 effect,則將其也添加進(jìn) effectList 中
  • 對(duì) effectList 進(jìn)行第一次遍歷,執(zhí)行 commitBeforeMutationEffects 函數(shù)來更新class組件實(shí)例上的state、props 等,以及執(zhí)行 getSnapshotBeforeUpdate 生命周期函數(shù)
  • 對(duì) effectList 進(jìn)行第二次遍歷,執(zhí)行 commitMutationEffects 函數(shù)來完成副作用的執(zhí)行,主要包括重置文本節(jié)點(diǎn)以及真實(shí) dom 節(jié)點(diǎn)的插入、刪除和更新等操作。
  • 對(duì) effectList 進(jìn)行第三次遍歷,執(zhí)行 commitLayoutEffects 函數(shù),去觸發(fā) componentDidMount、componentDidUpdate 以及各種回調(diào)函數(shù)等
  • 最后進(jìn)行一點(diǎn)變量還原之類的收尾,就完成了 commit 階段

我們從 commit 階段的入口函數(shù) commitRoot 開始看:

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRoot(root) {
  const renderPriorityLevel = getCurrentPriorityLevel();
  runWithPriority(
    ImmediateSchedulerPriority,
    commitRootImpl.bind(null, root, renderPriorityLevel),
  );
  return null;
}

它調(diào)用了 commitRootImpl 函數(shù),所要做的工作都在這個(gè)函數(shù)中:

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitRootImpl(root, renderPriorityLevel) {
  // ...
  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;
  // ...
  // 獲取 effectList 鏈表
  let firstEffect;
  if (finishedWork.flags > PerformedWork) {
    // 如果 root 上有 effect,則將其添加進(jìn) effectList 鏈表中
    if (finishedWork.lastEffect !== null) {
      finishedWork.lastEffect.nextEffect = finishedWork;
      firstEffect = finishedWork.firstEffect;
    } else {
      firstEffect = finishedWork;
    }
  } else {
    // 如果 root 上沒有 effect,直接使用 finishedWork.firstEffect 作用鏈表頭節(jié)點(diǎn)
    firstEffect = finishedWork.firstEffect;
  }
  if (firstEffect !== null) {
    // ...
    // 第一次遍歷,執(zhí)行 commitBeforeMutationEffects
    nextEffect = firstEffect;
    do {
      if (__DEV__) {
        invokeGuardedCallback(null, commitBeforeMutationEffects, null);
        // ...
      } else {
        try {
          commitBeforeMutationEffects();
        } catch (error) {
          // ...
        }
      }
    } while (nextEffect !== null);
    // ...
    // 第二次遍歷,執(zhí)行 commitMutationEffects
    nextEffect = firstEffect;
    do {
      if (__DEV__) {
        invokeGuardedCallback(
          null,
          commitMutationEffects,
          null,
          root,
          renderPriorityLevel,
        );
        // ...
      } else {
        try {
          commitMutationEffects(root, renderPriorityLevel);
        } catch (error) {
          // ...
        }
      }
    } while (nextEffect !== null);
    // 第三次遍歷,執(zhí)行 commitLayoutEffects
    nextEffect = firstEffect;
    do {
      if (__DEV__) {
        invokeGuardedCallback(null, commitLayoutEffects, null, root, lanes);
        // ...
      } else {
        try {
          commitLayoutEffects(root, lanes);
        } catch (error) {
          // ...
        }
      }
    } while (nextEffect !== null);
    nextEffect = null;
    // ...
  } else {
    // 沒有任何副作用
    root.current = finishedWork;
    if (enableProfilerTimer) {
      recordCommitTime();
    }
  }
  // ...
}

commitBeforeMutationEffects

commitBeforeMutationEffects 中,會(huì)從 firstEffect 開始,通過 nextEffect 不斷對(duì) effectList 鏈表進(jìn)行遍歷,若是當(dāng)前的 fiber 節(jié)點(diǎn)有 flags 副作用,則執(zhí)行 commitBeforeMutationEffectOnFiber 節(jié)點(diǎn)去對(duì)針對(duì) class 組件單獨(dú)處理。

相關(guān)參考視頻講解:傳送門

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    // ...
    const flags = nextEffect.flags;
    if ((flags & Snapshot) !== NoFlags) {
      // 如果當(dāng)前 fiber 節(jié)點(diǎn)有 flags 副作用
      commitBeforeMutationEffectOnFiber(current, nextEffect);
      // ...
    }
    // ...
    nextEffect = nextEffect.nextEffect;
  }
}

然后看一下 commitBeforeMutationEffectOnFiber,它里面根據(jù) fiber 的 tag 屬性,主要是對(duì) ClassComponent 組件進(jìn)行處理,更新 ClassComponent 實(shí)例上的state、props 等,以及執(zhí)行 getSnapshotBeforeUpdate 生命周期函數(shù):

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitBeforeMutationLifeCycles(
  current: Fiber | null,  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block: {
      return;
    }
    case ClassComponent: {
      if (finishedWork.flags & Snapshot) {
        if (current !== null) {
          // 非首次加載的情況下
          // 獲取上一次的 props 和 state
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          // 獲取當(dāng)前 class 組件實(shí)例
          const instance = finishedWork.stateNode;
          // ...
          // 調(diào)用 getSnapshotBeforeUpdate 生命周期方法
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          // ...
          // 將生成的 snapshot 保存到 instance.__reactInternalSnapshotBeforeUpdate 上,供 DidUpdate 生命周期使用
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
      }
      return;
    }
    // ...
  }
}

commitMutationEffects

commitMutationEffects 中會(huì)根據(jù)對(duì) effectList 進(jìn)行第二次遍歷,根據(jù) flags 的類型進(jìn)行二進(jìn)制與操作,然后根據(jù)結(jié)果去執(zhí)行不同的操作,對(duì)真實(shí) dom 進(jìn)行修改:相關(guān)參考視頻講解:進(jìn)入學(xué)習(xí)

  • ContentReset: 如果 flags 中包含 ContentReset 類型,代表文本節(jié)點(diǎn)內(nèi)容改變,則執(zhí)行 commitResetTextContent 重置文本節(jié)點(diǎn)的內(nèi)容
  • Ref: 如果 flags 中包含 Ref 類型,則執(zhí)行 commitDetachRef 更改 ref 對(duì)應(yīng)的 current 的值
  • Placement: 上一章 diff 中講過 Placement 代表插入,會(huì)執(zhí)行 commitPlacement 去插入 dom 節(jié)點(diǎn)
  • Update: flags 包含 Update 則會(huì)執(zhí)行 commitWork 執(zhí)行更新操作
  • Deletion: flags 包含 Deletion 則會(huì)執(zhí)行 commitDeletion 執(zhí)行更新操作
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitMutationEffects(
  root: FiberRoot,  renderPriorityLevel: ReactPriorityLevel,
) {
  // 對(duì) effectList 進(jìn)行遍歷
  while (nextEffect !== null) {
    setCurrentDebugFiberInDEV(nextEffect);
    const flags = nextEffect.flags;
    // ContentReset:重置文本節(jié)點(diǎn)
    if (flags & ContentReset) {
      commitResetTextContent(nextEffect);
    }
    // Ref:commitDetachRef 更新 ref 的 current 值
    if (flags & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
      if (enableScopeAPI) {
        if (nextEffect.tag === ScopeComponent) {
          commitAttachRef(nextEffect);
        }
      }
    }
    // 執(zhí)行更新、插入、刪除操作
    const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
    switch (primaryFlags) {
      case Placement: {
        // 插入
        commitPlacement(nextEffect);
        nextEffect.flags &= ~Placement;
        break;
      }
      case PlacementAndUpdate: {
        // 插入并更新
        // 插入
        commitPlacement(nextEffect);
        nextEffect.flags &= ~Placement;
        // 更新
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // ...
      case Update: {
        // 更新
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      case Deletion: {
        // 刪除
        commitDeletion(root, nextEffect, renderPriorityLevel);
        break;
      }
    }
    resetCurrentDebugFiberInDEV();
    nextEffect = nextEffect.nextEffect;
  }
}

下面我們重點(diǎn)來看一下 react 是如何對(duì)真實(shí) dom 節(jié)點(diǎn)進(jìn)行操作的。

插入 dom 節(jié)點(diǎn)

獲取父節(jié)點(diǎn)及插入位置

插入 dom 節(jié)點(diǎn)的操作以 commitPlacement 為入口函數(shù), commitPlacement 中會(huì)首先獲取當(dāng)前 fiber 的父 fiber 對(duì)應(yīng)的真實(shí) dom 節(jié)點(diǎn)以及在父節(jié)點(diǎn)下要插入的位置,根據(jù)父節(jié)點(diǎn)對(duì)應(yīng)的 dom 是否為 container,去執(zhí)行 insertOrAppendPlacementNodeIntoContainer 或者 insertOrAppendPlacementNode 進(jìn)行節(jié)點(diǎn)的插入。

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitPlacement(finishedWork: Fiber): void {
  if (!supportsMutation) {
    return;
  }
  // 獲取當(dāng)前 fiber 的父 fiber
  const parentFiber = getHostParentFiber(finishedWork);
  let parent;
  let isContainer;
  // 獲取父 fiber 對(duì)應(yīng)真實(shí) dom 節(jié)點(diǎn)
  const parentStateNode = parentFiber.stateNode;
  // 獲取父 fiber 對(duì)應(yīng)的 dom 是否可以作為 container
    case HostComponent:
      parent = parentStateNode;
      isContainer = false;
      break;
    case HostRoot:
      parent = parentStateNode.containerInfo;
      isContainer = true;
      break;
    case HostPortal:
      parent = parentStateNode.containerInfo;
      isContainer = true;
      break;
    case FundamentalComponent:
      if (enableFundamentalAPI) {
        parent = parentStateNode.instance;
        isContainer = false;
      }
    default:
      invariant(
        false,
        'Invalid host parent fiber. This error is likely caused by a bug ' +
          'in React. Please file an issue.',
      );
  }
  // 如果父 fiber 有 ContentReset 的 flags 副作用,則重置其文本內(nèi)容
  if (parentFiber.flags & ContentReset) {
    resetTextContent(parent);
    parentFiber.flags &= ~ContentReset;
  }
  // 獲取要在哪個(gè)兄弟 fiber 之前插入
  const before = getHostSibling(finishedWork);
  if (isContainer) {
    insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
  } else {
    insertOrAppendPlacementNode(finishedWork, before, parent);
  }
}

判斷當(dāng)前節(jié)點(diǎn)是否為單節(jié)點(diǎn)

我們以 insertOrAppendPlacementNodeIntoContainer 為例看一下其源碼,里面通過 tag 屬性判斷了當(dāng)前的 fiber 是否為原生 dom 節(jié)點(diǎn)。若是,則調(diào)用 insertInContainerBeforeappendChildToContainer 在相應(yīng)位置插入真實(shí) dom;若不是,則對(duì)當(dāng)前 fiber 的所有子 fiber 調(diào)用 insertOrAppendPlacementNodeIntoContainer 進(jìn)行遍歷:

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function insertOrAppendPlacementNodeIntoContainer(
  node: Fiber,  before: ?Instance,  parent: Container,
): void {
  const {tag} = node;
  // 判斷當(dāng)前節(jié)點(diǎn)是否為原生的 dom 節(jié)點(diǎn)
  const isHost = tag === HostComponent || tag === HostText;
  if (isHost || (enableFundamentalAPI && tag === FundamentalComponent)) {
    // 是原生 dom 節(jié)點(diǎn),在父節(jié)點(diǎn)的對(duì)應(yīng)位置插入當(dāng)前節(jié)點(diǎn)
    const stateNode = isHost ? node.stateNode : node.stateNode.instance;
    if (before) {
      insertInContainerBefore(parent, stateNode, before);
    } else {
      appendChildToContainer(parent, stateNode);
    }
  } else if (tag === HostPortal) {
    // 如是 Portal 不做處理
  } else {
    // 不是原生 dom 節(jié)點(diǎn),則遍歷插入當(dāng)前節(jié)點(diǎn)的各個(gè)子節(jié)點(diǎn)
    const child = node.child;
    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      let sibling = child.sibling;
      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

在對(duì)應(yīng)位置插入節(jié)點(diǎn)

before 不為 null 時(shí),說明要在某個(gè) dom 節(jié)點(diǎn)之前插入新的 dom,調(diào)用 insertInContainerBefore 去進(jìn)行插入,根據(jù)父節(jié)點(diǎn)是否注釋類型,選擇在父節(jié)點(diǎn)的父節(jié)點(diǎn)下插入新的 dom,還是直接在父節(jié)點(diǎn)下插入新的 dom:

// packages/react-dom/src/client/ReactDOMHostConfig.js
export function insertInContainerBefore(
  container: Container,  child: Instance | TextInstance,  beforeChild: Instance | TextInstance | SuspenseInstance,
): void {
  if (container.nodeType === COMMENT_NODE) {
    // 如果父節(jié)點(diǎn)為注釋類型,則在父節(jié)點(diǎn)的父節(jié)點(diǎn)下插入新的 dom
    (container.parentNode: any).insertBefore(child, beforeChild);
  } else {
    // 否則直接插入新的 dom
    container.insertBefore(child, beforeChild);
  }
}

before 為 null 時(shí),調(diào)用 appendChildToContainer 方法,直接在父節(jié)點(diǎn)(如果父節(jié)點(diǎn)為注釋類型則在父節(jié)點(diǎn)的父節(jié)點(diǎn))的最后位置插入新的 dom:

export function appendChildToContainer(
  container: Container,  child: Instance | TextInstance,
): void {
  let parentNode;
  if (container.nodeType === COMMENT_NODE) {
    // 如果父節(jié)點(diǎn)為注釋類型,則在父節(jié)點(diǎn)的父節(jié)點(diǎn)下插入新的 dom
    parentNode = (container.parentNode: any);
    parentNode.insertBefore(child, container);
  } else {
    // 否則直接插入新的 dom
    parentNode = container;
    parentNode.appendChild(child);
  }
  // ...
}

這幾步都是以 insertOrAppendPlacementNodeIntoContainer 為例看源碼,insertOrAppendPlacementNode 和它的唯一區(qū)別就是最后在對(duì)應(yīng)位置插入節(jié)點(diǎn)時(shí),不需要額外判斷父節(jié)點(diǎn) (container) 是否為 COMMENT_TYPE 了。

更新 dom 節(jié)點(diǎn)

更新操作以 commitWork 為入口函數(shù),更新主要是針對(duì) HostComponent 和 HostText 兩種類型進(jìn)行更新。

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
  // ...
  switch (finishedWork.tag) {
    // ...
    case ClassComponent: {
      return;
    }
    case HostComponent: {
      // 獲取真實(shí) dom 節(jié)點(diǎn)
      const instance: Instance = finishedWork.stateNode;
      if (instance != null) {
        // 獲取新的 props
        const newProps = finishedWork.memoizedProps;
        // 獲取老的 props
        const oldProps = current !== null ? current.memoizedProps : newProps;
        const type = finishedWork.type;
        // 取出 updateQueue
        const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
        // 清空 updateQueue
        finishedWork.updateQueue = null;
        if (updatePayload !== null) {
          // 提交更新
          commitUpdate(
            instance,
            updatePayload,
            type,
            oldProps,
            newProps,
            finishedWork,
          );
        }
      }
      return;
    }
    case HostText: {
      // 獲取真實(shí)文本節(jié)點(diǎn)
      const textInstance: TextInstance = finishedWork.stateNode;
      // 獲取新的文本內(nèi)容
      const newText: string = finishedWork.memoizedProps;
      // 獲取老的文本內(nèi)容
      const oldText: string =
        current !== null ? current.memoizedProps : newText;
      // 提交更新
      commitTextUpdate(textInstance, oldText, newText);
      return;
    }
    case HostRoot: {
      // ssr操作,暫不關(guān)注
      if (supportsHydration) {
        const root: FiberRoot = finishedWork.stateNode;
        if (root.hydrate) {
          root.hydrate = false;
          commitHydratedContainer(root.containerInfo);
        }
      }
      return;
    }
    case Profiler: {
      return;
    }
    // ...
}

更新 HostComponent

根據(jù)上面的 commitWork 的源碼,更新 HostComponent 時(shí),獲取了真實(shí) dom 節(jié)點(diǎn)實(shí)例、props 以及 updateQueue 之后,就調(diào)用 commitUpdate 對(duì) dom 進(jìn)行更新,它通過 updateProperties 函數(shù)將 props 變化應(yīng)用到真實(shí) dom 上。

// packages/react-dom/src/client/ReactDOMHostConfig.js
export function commitUpdate(
  domElement: Instance,  updatePayload: Array<mixed>,  type: string,  oldProps: Props,  newProps: Props,  internalInstanceHandle: Object,
): void {
  // 做了 domElement[internalPropsKey] = props 的操作
  updateFiberProps(domElement, newProps);
  // 應(yīng)用給真實(shí) dom
  updateProperties(domElement, updatePayload, type, oldProps, newProps);
}

updateProperties 中,通過 updateDOMProperties 將 diff 結(jié)果應(yīng)用于真實(shí)的 dom 節(jié)點(diǎn)。另外根據(jù) fiber 的 tag 屬性,如果判斷對(duì)應(yīng)的 dom 的節(jié)點(diǎn)為表單類型,例如 radio、textarea、input、select 等,會(huì)做特定的處理:

// packages/react-dom/src/client/ReactDOMComponent.js
export function updateProperties(
  domElement: Element,  updatePayload: Array<any>,  tag: string,  lastRawProps: Object,  nextRawProps: Object,
): void {
  // 針對(duì)表單組件進(jìn)行特殊處理,例如更新 radio 的 checked 值
  if (
    tag === 'input' &&
    nextRawProps.type === 'radio' &&
    nextRawProps.name != null
  ) {
    ReactDOMInputUpdateChecked(domElement, nextRawProps);
  }
  // 判斷是否為用戶自定義的組件,即是否包含 "-"
  const wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
  const isCustomComponentTag = isCustomComponent(tag, nextRawProps);
  // 將 diff 結(jié)果應(yīng)用于真實(shí) dom
  updateDOMProperties(
    domElement,
    updatePayload,
    wasCustomComponentTag,
    isCustomComponentTag,
  );
  // 針對(duì)表單的特殊處理
  switch (tag) {
    case 'input':
      ReactDOMInputUpdateWrapper(domElement, nextRawProps);
      break;
    case 'textarea':
      ReactDOMTextareaUpdateWrapper(domElement, nextRawProps);
      break;
    case 'select':
      ReactDOMSelectPostUpdateWrapper(domElement, nextRawProps);
      break;
  }
}

updateDOMProperties 中,會(huì)遍歷之前 render 階段生成的 updatePayload,將其映射到真實(shí)的 dom 節(jié)點(diǎn)屬性上,另外會(huì)針對(duì) style、dangerouslySetInnerHTML 以及 textContent 做一些處理,從而實(shí)現(xiàn)了 dom 的更新:

// packages/react-dom/src/client/ReactDOMHostConfig.js
function updateDOMProperties(
  domElement: Element,  updatePayload: Array<any>,  wasCustomComponentTag: boolean,  isCustomComponentTag: boolean,
): void {
  // 對(duì) updatePayload 遍歷
  for (let i = 0; i < updatePayload.length; i += 2) {
    const propKey = updatePayload[i];
    const propValue = updatePayload[i + 1];
    if (propKey === STYLE) {
      // 處理 style 樣式更新
      setValueForStyles(domElement, propValue);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      // 處理 innerHTML 改變
      setInnerHTML(domElement, propValue);
    } else if (propKey === CHILDREN) {
      // 處理 textContent
      setTextContent(domElement, propValue);
    } else {
      // 處理其他節(jié)點(diǎn)屬性
      setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
    }
  }
}

更新 HostText

HostText 的更新處理十分簡(jiǎn)單,調(diào)用 commitTextUpdate,里面直接將 dom 的 nodeValue 設(shè)置為 newText 的值:

// packages/react-dom/src/client/ReactDOMHostConfig.js
export function commitTextUpdate(
  textInstance: TextInstance,  oldText: string,  newText: string,
): void {
  textInstance.nodeValue = newText;
}

刪除 dom 節(jié)點(diǎn)

刪除 dom 節(jié)點(diǎn)的操作以 commitDeletion 為入口函數(shù),它所要做的事情最復(fù)雜。react 會(huì)采用深度優(yōu)先遍歷去遍歷整顆 fiber 樹,找到需要?jiǎng)h除的 fiber,除了要將對(duì)應(yīng)的 dom 節(jié)點(diǎn)刪除,還需要考慮 ref 的卸載、componentWillUnmount 等生命周期的調(diào)用:

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitDeletion(
  finishedRoot: FiberRoot,  current: Fiber,  renderPriorityLevel: ReactPriorityLevel,
): void {
  if (supportsMutation) {
    // 支持 useMutation
    unmountHostComponents(finishedRoot, current, renderPriorityLevel);
  } else {
    // 不支持 useMutation
    commitNestedUnmounts(finishedRoot, current, renderPriorityLevel);
  }
  const alternate = current.alternate;
  // 重置 fiber 的各項(xiàng)屬性
  detachFiberMutation(current);
  if (alternate !== null) {
    detachFiberMutation(alternate);
  }
}

unmountHostComponents

unmountHostComponents 首先判斷當(dāng)前父節(jié)點(diǎn)是否合法,若是不合法尋找合法的父節(jié)點(diǎn),然后通過深度優(yōu)先遍歷,去遍歷整棵樹,通過 commitUnmount 卸載 ref、執(zhí)行生命周期。遇到是原生 dom 類型的節(jié)點(diǎn),還會(huì)從對(duì)應(yīng)的父節(jié)點(diǎn)下刪除該節(jié)點(diǎn)。

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function unmountHostComponents(
  finishedRoot: FiberRoot,  current: Fiber,  renderPriorityLevel: ReactPriorityLevel,
): void {
  let node: Fiber = current;
  let currentParentIsValid = false;
  let currentParent;
  let currentParentIsContainer;
  while (true) {
    if (!currentParentIsValid) {
      // 若當(dāng)前的父節(jié)點(diǎn)不是非法的 dom 節(jié)點(diǎn),尋找一個(gè)合法的 dom 父節(jié)點(diǎn)
      let parent = node.return;
      findParent: while (true) {
        invariant(
          parent !== null,
          'Expected to find a host parent. This error is likely caused by ' +
            'a bug in React. Please file an issue.',
        );
        const parentStateNode = parent.stateNode;
        switch (parent.tag) {
          case HostComponent:
            currentParent = parentStateNode;
            currentParentIsContainer = false;
            break findParent;
          case HostRoot:
            currentParent = parentStateNode.containerInfo;
            currentParentIsContainer = true;
            break findParent;
          case HostPortal:
            currentParent = parentStateNode.containerInfo;
            currentParentIsContainer = true;
            break findParent;
          case FundamentalComponent:
            if (enableFundamentalAPI) {
              currentParent = parentStateNode.instance;
              currentParentIsContainer = false;
            }
        }
        parent = parent.return;
      }
      currentParentIsValid = true;
    }
    if (node.tag === HostComponent || node.tag === HostText) {
      // 若果是原生 dom 節(jié)點(diǎn),調(diào)用 commitNestedUnmounts 方法
      commitNestedUnmounts(finishedRoot, node, renderPriorityLevel);
      if (currentParentIsContainer) {
        // 若當(dāng)前的 parent 是 container,則將 child 從 container 中移除(通過 dom.removeChild 方法)
        removeChildFromContainer(
          ((currentParent: any): Container),
          (node.stateNode: Instance | TextInstance),
        );
      } else {
        // 從 parent 中移除 child(通過 dom.removeChild 方法)
        removeChild(
          ((currentParent: any): Instance),
          (node.stateNode: Instance | TextInstance),
        );
      }
    } // ...
    else if (node.tag === HostPortal) {
      // 若是 portal 節(jié)點(diǎn),直接向下遍歷 child,因?yàn)樗鼪]有 ref 和生命周期等額外要處理的事情
      if (node.child !== null) {
        currentParent = node.stateNode.containerInfo;
        currentParentIsContainer = true;
        node.child.return = node;
        node = node.child;
        continue;
      }
    } else {
      // 其他 react 節(jié)點(diǎn),調(diào)用 commitUnmount,里面會(huì)卸載 ref、執(zhí)行生命周期等
      commitUnmount(finishedRoot, node, renderPriorityLevel);
      // 深度優(yōu)先遍歷子節(jié)點(diǎn)
      if (node.child !== null) {
        node.child.return = node;
        node = node.child;
        continue;
      }
    }
    // node 和 current 相等時(shí)說明整顆樹的深度優(yōu)先遍歷完成
    if (node === current) {
      return;
    }
    // 如果沒有兄弟節(jié)點(diǎn),說明當(dāng)前子樹遍歷完畢,返回到父節(jié)點(diǎn)繼續(xù)深度優(yōu)先遍歷
    while (node.sibling === null) {
      if (node.return === null || node.return === current) {
        return;
      }
      node = node.return;
      if (node.tag === HostPortal) {
        currentParentIsValid = false;
      }
    }
    // 繼續(xù)遍歷兄弟節(jié)點(diǎn)
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

commitNestedUnmounts

commitNestedUnmounts 相比 unmountHostComponents 不需要額外做當(dāng)前父節(jié)點(diǎn)是否合法的判斷以及 react 節(jié)點(diǎn)類型的判斷,直接采用深度優(yōu)先遍歷,去執(zhí)行 commitUnmount 方法即可:

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitNestedUnmounts(
  finishedRoot: FiberRoot,  root: Fiber,  renderPriorityLevel: ReactPriorityLevel,
): void {
  let node: Fiber = root;
  while (true) {
    // 調(diào)用 commitUnmount 去卸載 ref、執(zhí)行生命周期
    commitUnmount(finishedRoot, node, renderPriorityLevel);
    if (
      node.child !== null &&
      (!supportsMutation || node.tag !== HostPortal)
    ) {
      // 深度優(yōu)先遍歷向下遍歷子樹
      node.child.return = node;
      node = node.child;
      continue;
    }
    if (node === root) {
      // node 為 root 時(shí)說明整棵樹的深度優(yōu)先遍歷完成
      return;
    }
    while (node.sibling === null) {
      // node.sibling 為 null 時(shí)說明當(dāng)前子樹遍歷完成,返回上級(jí)節(jié)點(diǎn)繼續(xù)深度優(yōu)先遍歷
      if (node.return === null || node.return === root) {
        return;
      }
      node = node.return;
    }
    // 遍歷兄弟節(jié)點(diǎn)
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

commitUnmount

commitUnmount 中會(huì)完成對(duì) react 組件 ref 的卸載,若果是類組件,執(zhí)行 componentWillUnmount 生命周期等操作:

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitUnmount(
  finishedRoot: FiberRoot,  current: Fiber,  renderPriorityLevel: ReactPriorityLevel,
): void {
  onCommitUnmount(current);
  switch (current.tag) {
    case FunctionComponent:
    case ForwardRef:
    case MemoComponent:
    case SimpleMemoComponent:
    // ...
    case ClassComponent: {
      // 卸載 ref
      safelyDetachRef(current);
      const instance = current.stateNode;
      // 執(zhí)行 componentWillUnmount 生命周期
      if (typeof instance.componentWillUnmount === 'function') {
        safelyCallComponentWillUnmount(current, instance);
      }
      return;
    }
    case HostComponent: {
      // 卸載 ref
      safelyDetachRef(current);
      return;
    }
    case HostPortal: {
      if (supportsMutation) {
        // 遞歸遍歷子樹
        unmountHostComponents(finishedRoot, current, renderPriorityLevel);
      } else if (supportsPersistence) {
        emptyPortalContainer(current);
      }
      return;
    }
    // ...
  }
}

最終通過以上操作,react 就完成了 dom 的刪除工作。

commitLayoutEffects

接下來通過 commitLayoutEffects 為入口函數(shù),執(zhí)行第三次遍歷,這里會(huì)遍歷 effectList,執(zhí)行 componentDidMountcomponentDidUpdate 等生命周期,另外會(huì)執(zhí)行 componentUpdateQueue 函數(shù)去執(zhí)行回調(diào)函數(shù)。

// packages/react-reconciler/src/ReactFiberWorkLoop.old.js
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
  // ...
  // 遍歷 effectList
  while (nextEffect !== null) {
    setCurrentDebugFiberInDEV(nextEffect);
    const flags = nextEffect.flags;
    if (flags & (Update | Callback)) {
      const current = nextEffect.alternate;
      // 執(zhí)行 componentDidMount、componentDidUpdate 以及 componentUpdateQueue
      commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
    }
    // 更新 ref
    if (enableScopeAPI) {
      if (flags & Ref && nextEffect.tag !== ScopeComponent) {
        commitAttachRef(nextEffect);
      }
    } else {
      if (flags & Ref) {
        commitAttachRef(nextEffect);
      }
    }
    resetCurrentDebugFiberInDEV();
    nextEffect = nextEffect.nextEffect;
  }
}

執(zhí)行生命周期

commitLayoutEffectOnFiber 調(diào)用了 packages/react-reconciler/src/ReactFiberCommitWork.old.js 路徑下的 commitLifeCycles 函數(shù),里面針對(duì)首次渲染和非首次渲染分別執(zhí)行 componentDidMountcomponentDidUpdate 生命周期,以及調(diào)用 commitUpdateQueue 去觸發(fā)回調(diào):

// packages/react-reconciler/src/ReactFiberCommitWork.old.js
function commitLifeCycles(
  finishedRoot: FiberRoot,  current: Fiber | null,  finishedWork: Fiber,  committedLanes: Lanes,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    // ...
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.flags & Update) {
        if (current === null) {
          // 首次渲染,執(zhí)行 componentDidMount 生命周期
          if (
            enableProfilerTimer &&
            enableProfilerCommitHooks &&
            finishedWork.mode & ProfileMode
          ) {
            try {
              startLayoutEffectTimer();
              instance.componentDidMount();
            } finally {
              recordLayoutEffectDuration(finishedWork);
            }
          } else {
            instance.componentDidMount();
          }
        } else {
          // 非首次渲染,執(zhí)行 componentDidUpdate 生命周期
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;
          // ...
          if (
            enableProfilerTimer &&
            enableProfilerCommitHooks &&
            finishedWork.mode & ProfileMode
          ) {
            try {
              startLayoutEffectTimer();
              instance.componentDidUpdate(
                prevProps,
                prevState,
                instance.__reactInternalSnapshotBeforeUpdate,
              );
            } finally {
              recordLayoutEffectDuration(finishedWork);
            }
          } else {
            instance.componentDidUpdate(
              prevProps,
              prevState,
              instance.__reactInternalSnapshotBeforeUpdate,
            );
          }
        }
      }
      // ...
      if (updateQueue !== null) {
        // ...
        // 執(zhí)行 commitUpdateQueue 處理回調(diào)
        commitUpdateQueue(finishedWork, updateQueue, instance);
      }
      return;
    }
    case HostRoot: {
      const updateQueue: UpdateQueue<
        *,
      > | null = (finishedWork.updateQueue: any);
      if (updateQueue !== null) {
        // ...
        // 調(diào)用 commitUpdateQueue 處理 ReactDOM.render 的回調(diào)
        commitUpdateQueue(finishedWork, updateQueue, instance);
      }
      return;
    }
    case HostComponent: {
      const instance: Instance = finishedWork.stateNode;
      // ...
      // commitMount 處理 input 標(biāo)簽有 auto-focus 的情況
      if (current === null && finishedWork.flags & Update) {
        const type = finishedWork.type;
        const props = finishedWork.memoizedProps;
        commitMount(instance, type, props, finishedWork);
      }
      return;
    }
    // ...
}

處理回調(diào)

處理回調(diào)是在 commitUpdateQueue 中做的,它會(huì)對(duì) finishedQueue 上面的 effects 進(jìn)行遍歷,若有 callback,則執(zhí)行 callback。同時(shí)會(huì)重置 finishedQueue 上面的 effects 為 null:

// packages/react-reconciler/src/ReactUpdateQueue.old.js
export function commitUpdateQueue<State>(
  finishedWork: Fiber,
  finishedQueue: UpdateQueue<State>,
  instance: any,
): void {
  const effects = finishedQueue.effects;
  // 清空 effects
  finishedQueue.effects = null;
  // 對(duì) effect 遍歷
  if (effects !== null) {
    for (let i = 0; i < effects.length; i++) {
      const effect = effects[i];
      const callback = effect.callback;
      // 執(zhí)行回調(diào)
      if (callback !== null) {
        effect.callback = null;
        callCallback(callback, instance);
      }
    }
  }
}

在這之后就是進(jìn)行最后一點(diǎn)變量還原等收尾工作,然后整個(gè) commit 過程就完成了!

總結(jié)

接 render 階段的流程圖,補(bǔ)充上 commit 階段的流程圖,就構(gòu)成了完整的 react 執(zhí)行圖了:

到此這篇關(guān)于React commit源碼分析詳解的文章就介紹到這了,更多相關(guān)React commit內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • react實(shí)現(xiàn)消息顯示器

    react實(shí)現(xiàn)消息顯示器

    這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)消息顯示器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • 一起來了解React的Hook

    一起來了解React的Hook

    這篇文章主要為大家詳細(xì)介紹了React的Hook,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • 淺談React之狀態(tài)(State)

    淺談React之狀態(tài)(State)

    這篇文章主要介紹了淺談React之狀態(tài)(State),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-09-09
  • react-redux的connect用法詳解

    react-redux的connect用法詳解

    react-redux是react官方推出的redux綁定庫,React-Redux 將所有組件分成兩大類一個(gè)是UI組件和容器組件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-01-01
  • 使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過程詳解

    使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過程詳解

    這篇文章主要介紹了使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過程詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Shopee在React?Native?架構(gòu)方面的探索及發(fā)展歷程

    Shopee在React?Native?架構(gòu)方面的探索及發(fā)展歷程

    這篇文章主要介紹了Shopee在React?Native?架構(gòu)方面的探索,本文會(huì)從發(fā)展歷史、架構(gòu)模型、系統(tǒng)設(shè)計(jì)、遷移方案四個(gè)方向逐一介紹我們?nèi)绾我徊讲降貪M足多團(tuán)隊(duì)在復(fù)雜業(yè)務(wù)中的開發(fā)需求,需要的朋友可以參考下
    2022-07-07
  • React實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)hook組件實(shí)戰(zhàn)示例

    React實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)hook組件實(shí)戰(zhàn)示例

    這篇文章主要為大家介紹了React實(shí)現(xiàn)一個(gè)倒計(jì)時(shí)hook組件,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-02-02
  • React+TypeScript項(xiàng)目中使用CodeMirror的步驟

    React+TypeScript項(xiàng)目中使用CodeMirror的步驟

    CodeMirror被廣泛應(yīng)用于許多Web應(yīng)用程序和開發(fā)工具,之前做需求用到過codeMirror這個(gè)工具,覺得還不錯(cuò),功能很強(qiáng)大,所以記錄一下改工具的基礎(chǔ)用法,對(duì)React+TypeScript項(xiàng)目中使用CodeMirror的步驟感興趣的朋友跟隨小編一起看看吧
    2023-07-07
  • React Native中ScrollView組件輪播圖與ListView渲染列表組件用法實(shí)例分析

    React Native中ScrollView組件輪播圖與ListView渲染列表組件用法實(shí)例分析

    這篇文章主要介紹了React Native中ScrollView組件輪播圖與ListView渲染列表組件用法,結(jié)合實(shí)例形式詳細(xì)分析了ScrollView組件輪播圖與ListView渲染列表組件具體功能、使用方法與操作注意事項(xiàng),需要的朋友可以參考下
    2020-01-01
  • ReactHook使用useState更新變量后,如何拿到變量更新后的值

    ReactHook使用useState更新變量后,如何拿到變量更新后的值

    這篇文章主要介紹了ReactHook使用useState更新變量后,如何拿到變量更新后的值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03

最新評(píng)論