React render階段解析三-completeWork流程

這次是接著上一期render階段的文章秧饮,解析掛載時(shí)render階段的"歸"階段----completeWork函數(shù)

completeWork開始階段

在performUnitOfWork中執(zhí)行完beginWork,就會(huì)進(jìn)入下面判斷


workInProgress.child
  if (next === null) {
    // workInProgress已經(jīng)不存在子樹峰尝,就開始進(jìn)行"歸"階段
    completeUnitOfWork(unitOfWork);
  } else {
    // next是beginWork調(diào)用后的返回值workInProgress.child
    workInProgress = next;
  }

completeUnitOfWork

主要做了兩件事甘畅,執(zhí)行completeWork 和收攏EffectList

function completeUnitOfWork(unitOfWork: Fiber): void {
  //嘗試完成當(dāng)前的工作單元慨蓝,然后移動(dòng)到下一個(gè)兄弟剧防。 如果沒有更多的兄弟京革,返回到父fiber奇唤。
  let completedWork = unitOfWork;
  // 直到父節(jié)點(diǎn)為null,表示整棵 workInProgress fiber 樹已處理完畢匹摇。
  do {
    // 記錄父節(jié)點(diǎn)和當(dāng)前節(jié)點(diǎn)的current樹
    const current = completedWork.alternate;
    const returnFiber = completedWork.return;

    // 檢查工作是否完成或是否有東西拋出.
    if ((completedWork.effectTag & Incomplete) === NoEffect) {
      let next;
      if (
        !enableProfilerTimer ||
        (completedWork.mode & ProfileMode) === NoMode
      ) {
        // 執(zhí)行completeWork咬扇,并把返回值賦值給next
        next = completeWork(current, completedWork, subtreeRenderLanes);
      } else {
        startProfilerTimer(completedWork);
        next = completeWork(current, completedWork, subtreeRenderLanes);
        // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
      }
      resetCurrentDebugFiberInDEV();

      if (next !== null) {
        // Completing this fiber spawned new work. Work on that next.
        workInProgress = next;
        return;
      }

      resetChildLanes(completedWork);

      if (
        returnFiber !== null &&
        (returnFiber.effectTag & Incomplete) === NoEffect
      ) {
        // 執(zhí)行effect相關(guān)
        if (returnFiber.firstEffect === null) {
          returnFiber.firstEffect = completedWork.firstEffect;
        }
        if (completedWork.lastEffect !== null) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
          }
          returnFiber.lastEffect = completedWork.lastEffect;
        }

        if (effectTag > PerformedWork) {
          if (returnFiber.lastEffect !== null) {
            returnFiber.lastEffect.nextEffect = completedWork;
          } else {
            returnFiber.firstEffect = completedWork;
          }
          returnFiber.lastEffect = completedWork;
        }
      }
    } else {
      const next = unwindWork(completedWork, subtreeRenderLanes);

      // Because this fiber did not complete, don't reset its expiration time.

      if (next !== null) {
        next.effectTag &= HostEffectMask;
        workInProgress = next;
        return;
      }

      if (
        enableProfilerTimer &&
        (completedWork.mode & ProfileMode) !== NoMode
      ) {
       
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);

        let actualDuration = completedWork.actualDuration;
        let child = completedWork.child;
        while (child !== null) {
          actualDuration += child.actualDuration;
          child = child.sibling;
        }
        completedWork.actualDuration = actualDuration;
      }

      if (returnFiber !== null) {
        // Mark the parent fiber as incomplete and clear its effect list.
        returnFiber.firstEffect = returnFiber.lastEffect = null;
        returnFiber.effectTag |= Incomplete;
      }
    }

    const siblingFiber = completedWork.sibling;
    if (siblingFiber !== null) {
      // If there is more work to do in this returnFiber, do that next.
      workInProgress = siblingFiber;
      return;
    }
    // 賦值父節(jié)點(diǎn)
    completedWork = returnFiber;
    workInProgress = completedWork;
  } while (completedWork !== null);

  // We've reached the root.
  if (workInProgressRootExitStatus === RootIncomplete) {
    workInProgressRootExitStatus = RootCompleted;
  }
}

運(yùn)行流程

運(yùn)行流程

completeWork

如果說“遞”階段的 beginWork 方法主要是創(chuàng)建子節(jié)點(diǎn),那么“歸”階段的 completeWork 方法則主要是創(chuàng)建當(dāng)前節(jié)點(diǎn)的 DOM 節(jié)點(diǎn)来惧,并對(duì)子節(jié)點(diǎn)的 DOM 節(jié)點(diǎn)和 EffectList 進(jìn)行收攏冗栗。很多類型是不進(jìn)行處理,return null

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent: {
      // ...省略
      return null;
    }
    case HostRoot: {
      // ...省略
      return null;
    }
    case HostComponent: {
      // ...
      const type = workInProgress.type;
      if (current !== null && workInProgress.stateNode != null) {
      //更新dom節(jié)點(diǎn)
        updateHostComponent(
          current,
          workInProgress,
          type,
          newProps,
          rootContainerInstance,
        );

        if (enableDeprecatedFlareAPI) {
          const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
          const nextListeners = newProps.DEPRECATED_flareListeners;
          if (prevListeners !== nextListeners) {
            markUpdate(workInProgress);
          }
        }

        if (current.ref !== workInProgress.ref) {
          markRef(workInProgress);
        }
      } else {
        //...
        const currentHostContext = getHostContext();
        const wasHydrated = popHydrationState(workInProgress);
        if (wasHydrated) {
          // 服務(wù)端渲染相關(guān)
        } else {
          // 創(chuàng)建新的dom節(jié)點(diǎn)
          const instance = createInstance(
            type,
            newProps,
            rootContainerInstance,
            currentHostContext,
            workInProgress,
          );
          // 把fiber子節(jié)點(diǎn)的dom掛載到當(dāng)前dom后面
          appendAllChildren(instance, workInProgress, false, false);

          workInProgress.stateNode = instance;

          if (enableDeprecatedFlareAPI) {
            const listeners = newProps.DEPRECATED_flareListeners;
            if (listeners != null) {
              updateDeprecatedEventListeners(
                listeners,
                workInProgress,
                rootContainerInstance,
              );
            }
          }

          if (
            // 初始化dom屬性和事件
            finalizeInitialChildren(
              instance,
              type,
              newProps,
              rootContainerInstance,
              currentHostContext,
            )
          ) {
            markUpdate(workInProgress);
          }
        }

        if (workInProgress.ref !== null) {
          markRef(workInProgress);
        }
      }
      return null;
    }
  // ...省略
}
流程圖

createInstance

新建節(jié)點(diǎn)供搀,調(diào)用createElement創(chuàng)建dom節(jié)點(diǎn)

function createInstance(
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
  internalInstanceHandle: Object,
): Instance {
  let parentNamespace: string;
  if (__DEV__) {
    // TODO: take namespace into account when validating.
    const hostContextDev = ((hostContext: any): HostContextDev);
    validateDOMNesting(type, null, hostContextDev.ancestorInfo);
    if (
      typeof props.children === 'string' ||
      typeof props.children === 'number'
    ) {
      const string = '' + props.children;
      const ownAncestorInfo = updatedAncestorInfo(
        hostContextDev.ancestorInfo,
        type,
      );
      validateDOMNesting(null, string, ownAncestorInfo);
    }
    parentNamespace = hostContextDev.namespace;
  } else {
    parentNamespace = ((hostContext: any): HostContextProd);
  }
  const domElement: Instance = createElement(
    type,
    props,
    rootContainerInstance,
    parentNamespace,
  );
  precacheFiberNode(internalInstanceHandle, domElement);
  // 更新屬性
  updateFiberProps(domElement, props);
  return domElement;
}

appendAllChildren

把fiber子節(jié)點(diǎn)的dom掛載到當(dāng)前dom后面

 appendAllChildren = function(
    parent: Instance,
    workInProgress: Fiber,
    needsVisibilityToggle: boolean,
    isHidden: boolean,
  ) {
    let node = workInProgress.child;
    while (node !== null) {
      if (node.tag === HostComponent || node.tag === HostText) {
        // stateNode掛載節(jié)點(diǎn)的dom
        appendInitialChild(parent, node.stateNode);
      } else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
        appendInitialChild(parent, node.stateNode.instance);
      } else if (node.tag === HostPortal) {
      } else if (node.child !== null) {
        // 針對(duì)一些特殊類型的子節(jié)點(diǎn)隅居,如<Fragment />,嘗試從子節(jié)點(diǎn)的子節(jié)點(diǎn)獲取DOM
        // 存在子節(jié)點(diǎn)就繼續(xù)遍歷子節(jié)點(diǎn)
        node.child.return = node;
        node = node.child;
        continue;
      }
      if (node === workInProgress) {
        return;
      }
      while (node.sibling === null) {
        if (node.return === null || node.return === workInProgress) {
          return;
        }
        node = node.return;
      }
      node.sibling.return = node.return;
      // 將node.sibling作為下次循環(huán)的主體
      node = node.sibling;
    }
  };

// 執(zhí)行了原生的appendChild方法
export function appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void {
  parentInstance.appendChild(child);
}

updateHostComponent

更新舊的dom節(jié)點(diǎn)葛虐,主要作用就是計(jì)算出需要變化的 DOM 節(jié)點(diǎn)屬性胎源,并給當(dāng)前節(jié)點(diǎn)打上Update的EffectTag。

updateHostComponent = function(
    current: Fiber,
    workInProgress: Fiber,
    type: Type,
    newProps: Props,
    rootContainerInstance: Container,
  ) {
    // If we have an alternate, that means this is an update and we need to
    // schedule a side-effect to do the updates.
    const oldProps = current.memoizedProps;
    // props沒有變化就直接返回
    if (oldProps === newProps) {
      return;
    }

    
    const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // 將計(jì)算出來的updatePayload掛載在workInProgress.updateQueue上屿脐,供后續(xù)commit階段使用
    workInProgress.updateQueue = (updatePayload: any);
    // 如果updatePayload不為空涕蚤,則給當(dāng)前節(jié)點(diǎn)打上Update的EffectTag
    if (updatePayload) {
      markUpdate(workInProgress);
    }
  };

總結(jié)

  1. completeUnitOfWork方法主要循環(huán)執(zhí)行completeWork宪卿,父元素為空或者存在兄弟節(jié)點(diǎn)就會(huì)進(jìn)行下一輪render階段解析,生成兄弟節(jié)點(diǎn)的fiber万栅。
  2. completeWork主要是生成當(dāng)前fiber的dom節(jié)點(diǎn)佑钾,并且掛載連接子節(jié)點(diǎn)的dom
  3. completeWork主要使用createInstance新建節(jié)點(diǎn)和updateHostComponent更新節(jié)點(diǎn)操作。
  4. 最終結(jié)束completeUnitOfWork執(zhí)行烦粒,進(jìn)入commit階段(下個(gè)文章開始講休溶,敬請(qǐng)期待)
流程
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市扰她,隨后出現(xiàn)的幾起案子兽掰,更是在濱河造成了極大的恐慌,老刑警劉巖徒役,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽尽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡忧勿,警方通過查閱死者的電腦和手機(jī)杉女,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狐蜕,“玉大人宠纯,你說我怎么就攤上這事〔闶停” “怎么了婆瓜?”我有些...
    開封第一講書人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贡羔。 經(jīng)常有香客問我廉白,道長(zhǎng),這世上最難降的妖魔是什么乖寒? 我笑而不...
    開封第一講書人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任猴蹂,我火速辦了婚禮,結(jié)果婚禮上楣嘁,老公的妹妹穿的比我還像新娘磅轻。我一直安慰自己,他們只是感情好逐虚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開白布聋溜。 她就那樣靜靜地躺著,像睡著了一般叭爱。 火紅的嫁衣襯著肌膚如雪撮躁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評(píng)論 1 301
  • 那天买雾,我揣著相機(jī)與錄音把曼,去河邊找鬼杨帽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嗤军,可吹牛的內(nèi)容都是我干的注盈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼型雳,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼当凡!你這毒婦竟也來了山害?” 一聲冷哼從身側(cè)響起纠俭,我...
    開封第一講書人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浪慌,沒想到半個(gè)月后冤荆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡权纤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年钓简,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汹想。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡外邓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出古掏,到底是詐尸還是另有隱情损话,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布槽唾,位于F島的核電站丧枪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏庞萍。R本人自食惡果不足惜拧烦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钝计。 院中可真熱鬧恋博,春花似錦、人聲如沸私恬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)践付。三九已至秦士,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間永高,已是汗流浹背隧土。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工提针, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人曹傀。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓辐脖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親皆愉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗜价,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容