React源碼06 - 完成節(jié)點任務

06 - 完成節(jié)點任務

完成節(jié)點更新之后完成節(jié)點的創(chuàng)建,并提供優(yōu)化到最小幅度的DOM更新列表。

1. completeUnitOfWork

第 04 篇說過 renderRoot 做的事情:

  • 調(diào)用 workLoop 進行循環(huán)單元更新
  • 捕獲錯誤并進行處理
  • 走完流程之后善后

現(xiàn)在在 workLoop 中調(diào)用 performUnitOfWork会放。
next = beginWork(current, workInProgress, nextRenderExpirationTime)
每次都 return child,以便繼續(xù)循環(huán)向下單路查找钉凌,直到觸及葉子節(jié)點(樹枝到頭了就是葉子)返回 null咧最,也就是葉子節(jié)點沒有 child 了。這算是 renderRoot 中完成了一次 workLoop 調(diào)用。
此時矢沿,傳入該葉子節(jié)點執(zhí)行 completeUnitOfWork滥搭。
之后嘗試繼續(xù)循環(huán)執(zhí)行 wokrLoop,處理其他路捣鲸。

completeUnitOfWork:

    1. 根據(jù)是否中斷來調(diào)用不同的處理方法

在外部 renderRoot 使用 do...while 調(diào)用 workLoop 時會使用 try...catch瑟匆,只要不是致命錯誤,就記錄相應錯誤(比如 Suspense 的 promise 在中間過程中的合理報錯)栽惶,然后繼續(xù)執(zhí)行循環(huán)愁溜。

    1. 判斷是否有兄弟節(jié)點來執(zhí)行不同的操作
    1. 完成節(jié)點之后賦值 effect 鏈

在之前的 beginWork 中為節(jié)點標記了相應的 sideEffect,也就是等到 commit 階段中更新 dom 時的操作依據(jù)(增刪改等)外厂。而在 completeUnitOfWork 中則將 fiber 上的 sideEffect 進一步進行串聯(lián)冕象,方便 commit 時使用。
之前第 03 篇中說過每個 fiber 上都有:

  • effectTag: SideEffectTag汁蝶。用來記錄 SideEffect交惯。
  • nextEffect: Fiber | null。單鏈表用來快速查找下一個side effect穿仪。
  • firstEffect: Fiber | null席爽。 子樹中第一個side effect。
  • lastEffect: Fiber | null啊片。子樹中最后一個side effect只锻。

有些像是層層嵌套的文件夾 A/B/C/D,B中只記錄了C/D紫谷。這些打了 effectTag 標記的 fiber 節(jié)點通過這些指針單獨組成單向鏈表齐饮,反正都是些指針引用,也不占多少空間笤昨。

通過不斷地 completeUnitOfWork 將 effect 匯總串聯(lián)到上層節(jié)點祖驱,最終 RootFiber 上的 firstEffect 到 lastEffect 這個鏈表中記錄了所有帶有 effectTag 的 fiber 節(jié)點,即最終在 commit 階段所有需要應用到 dom 節(jié)點上的 SideEffect瞒窒。

TODO:commitRoot 方法
**
SideEffectTag 清單:

export type SideEffectTag = number;

// 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;

performUnitOfWork:

function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  // The current, flushed, state of this fiber is the alternate.
  // Ideally nothing should rely on this, but relying on it here
  // means that we don't need an additional field on the work in
  // progress.
  const current = workInProgress.alternate;

  // See if beginning this work spawns more work.
  startWorkTimer(workInProgress);

  let next;
  if (enableProfilerTimer) {
    if (workInProgress.mode & ProfileMode) {
      startProfilerTimer(workInProgress);
    }

    next = beginWork(current, workInPrognextRenderExpirationTimeress, );
    workInProgress.memoizedProps = workInProgress.pendingProps;

    if (workInProgress.mode & ProfileMode) {
      // Record the render duration assuming we didn't bailout (or error).
      stopProfilerTimerIfRunningAndRecordDelta(workInProgress, true);
    }
  } else {
    next = beginWork(current, workInProgress, nextRenderExpirationTime);
    workInProgress.memoizedProps = workInProgress.pendingProps;
  }

  if (next === null) {
    // 如果這次遍歷時調(diào)用beginWork返回了null捺僻,說明已經(jīng)到了單路的葉子節(jié)點了,于是調(diào)用completeUnitOfWork崇裁。
    // 當前的workInProgress就是葉子節(jié)點匕坯,因為尋找child卻返回了null,說明到頭了拔稳。
    // If this doesn't spawn new work, complete the current work.
    next = completeUnitOfWork(workInProgress);
  }

  ReactCurrentOwner.current = null;

  return next;
}

completeUnitOfWork:
如果說 workLoop 在局部子樹中是從上向下處理節(jié)點葛峻。那么 completeUnitOfWork 中則是在局部子樹中從下向上處理節(jié)點。

if (next === null) {
  // 如果這次遍歷時調(diào)用beginWork返回了null巴比,說明已經(jīng)到了單路的葉子節(jié)點了术奖,于是調(diào)用completeUnitOfWork礁遵。
  // 當前的workInProgress就是葉子節(jié)點,因為尋找child卻返回了null采记,說明到頭了佣耐。
  // If this doesn't spawn new work, complete the current work.
  next = completeUnitOfWork(workInProgress);
}

completeUnitOfWork 代碼有些多,主體是個 while 循環(huán)挺庞,其中有下面這個遍歷邏輯:

  • 在 performUnitOfWork 中,如果從上至下單路遍歷到了葉子節(jié)點稼病,則開始調(diào)用 completeUnitOfWork 進行向上遍歷选侨。
  • 如果有 sibling 兄弟節(jié)點則 return 兄弟節(jié)點,以便 workLoop 中再次調(diào)用 performUnitOfWork 對剛才的兄弟節(jié)點進行遍歷然走。
  • 又一次單路到頭了援制,遇到了葉子節(jié)點,則再次 completeUnitOfWork 處理葉子節(jié)點芍瑞。
  • 如果當前子樹兄弟節(jié)點全處理完了晨仑,則向上對父節(jié)點進行 completeUnitOfWork 處理。如果父節(jié)點也有兄弟節(jié)點拆檬,則同理洪己。

最終效果就是一整棵 RootFiber 樹:

  • 每個節(jié)點都會先使用 performUnitOfWork 處理一次。
  • 再使用 completeUnitOfWork 處理一次竟贯。

**
completeUnitOfWork 節(jié)選:

function completeUnitOfWork(workInProgress: Fiber): Fiber | null
// ...
if (siblingFiber !== null) {
  // If there is more work to do in this returnFiber, do that next.
  return siblingFiber;
} else if (returnFiber !== null) {
  // If there's no more work in this returnFiber. Complete the returnFiber.
  workInProgress = returnFiber;
  continue;
} else {
  // 說明更新過程完成了答捕,到rootFiber了。等著commit階段了
  return null;
}

completeUnitOfWork 中的一些工作:

  • 重設 ChildExpirationTime
  • completeWork

2. 重設 ChildExpirationTime

ChildExpirationTime 記錄了一個 fiber 的子樹中優(yōu)先級最高的更新時間屑那。盡管產(chǎn)生更新需求的節(jié)點可能是整個應用 fiber 樹中的某個節(jié)點拱镐,但進行更新調(diào)度時是從頂部 RootFiber 開始參與調(diào)度的。
因此通過 ChildExpirationTime 不斷向上匯總子樹的最高優(yōu)先級的更新時間持际,最終 RootFiber 的 ChildExpirationTime 記錄了整棵樹中最高優(yōu)先級的更新時間沃琅。
而在 completeUnitOfWork 從下向上進行信息匯總時,如果某個節(jié)點的更新任務已經(jīng)得到執(zhí)行蜘欲,也就是沒有自身的 expirationTime 了益眉,那么 completeUnitOfWork 中就需要順便不斷向上重置 ChildExpirationTime。通過調(diào)用:
resetChildExpirationTime(workInProgress, nextRenderExpirationTime);

3. completeWork

  • pop 各種 context 相關(guān)的內(nèi)容
  • 對于 HostComponent 執(zhí)行初始化
  • 初始化監(jiān)聽事件

大部分類型的 fiber 節(jié)點不需要在這一步做什么事情(Suspense 類型以后再說)姥份,而以下兩種類型有點東西:

  • HostComponent
  • HostText

下面主要談談 HostComponent(該 fiber 類型對應到原生 dom)在 completeWork 中的相關(guān)操作呜叫。

4. HostComponent

HostComponent 中涉及到 updateHostComponent。在一次更新而非渲染中:

  • diffProperties 計算需要更新的內(nèi)容
  • 不同的 dom property 處理方式不同

首次渲染時

創(chuàng)建對應的 dom 實例:

let instance = createInstance(
  type,
  newProps,
  rootContainerInstance,
  currentHostContext,
  workInProgress,
);

appendAllChildren(instance, workInProgress, false, false);

if (
  finalizeInitialChildren( // finalizeInitialChildren 最終返回是否需要 auto focus 自動聚焦
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext,
  )
) {
  markUpdate(workInProgress); // 如果是需要 autoFocus殿衰,那么還要設置 sideEffect朱庆。
}
workInProgress.stateNode = instance;
function markUpdate(workInProgress: Fiber) {
  // Tag the fiber with an update effect. This turns a Placement into
  // a PlacementAndUpdate.
  workInProgress.effectTag |= Update;
}

function markRef(workInProgress: Fiber) {
  workInProgress.effectTag |= Ref;
}

appendAllChildren:
找到子樹中第一層 dom 類型的節(jié)點,append 到自身對應的 dom 實例下闷祥。即 workInProgress.stateNode 所記錄的原生 dom 實例娱颊。
因為在每次 completeUnitOfWork 時遇到 HostComponent 即原生 dom 對應的 fiber 類型時傲诵,都會 appendAllChildren。
所以也就是對 fiber 樹這個虛擬 dom 進行提純箱硕,最終從下向上構(gòu)建出純 dom 樹拴竹。
**
finalizeInitialChildren:
appendAllChildren 之后緊接著 finalizeInitialChildren 初始化事件監(jiān)聽體系。
setInitialProperties 初始化事件監(jiān)聽(后面會單獨講事件監(jiān)聽)剧罩。
初始化 dom attribute(主要是一些原生 dom 操作)栓拜。
初始化markUpdate、mark*惠昔、 defaultValue幕与、isControlled、處理 style 屬性镇防,px 補全等啦鸣。

下面展開敘述。

finalizeInitialChildren 最終返回是否需要 auto focus 自動聚焦:

export function finalizeInitialChildren(
  domElement: Instance,
  type: string,
  props: Props,
  rootContainerInstance: Container,
  hostContext: HostContext,
): boolean {
  setInitialProperties(domElement, type, props, rootContainerInstance);
  return shouldAutoFocusHostComponent(type, props);
}

function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
  switch (type) {
    case 'button':
    case 'input':
    case 'select':
    case 'textarea':
      return !!props.autoFocus;
  }
  return false;
}

// ...
setInitialDOMProperties(
  tag,
  domElement,
  rootContainerElement,
  props,
  isCustomComponentTag,
);

setInitialDOMProperties:

function setInitialDOMProperties(
  tag: string,
  domElement: Element,
  rootContainerElement: Element | Document,
  nextProps: Object,
  isCustomComponentTag: boolean,
): void {
  for (const propKey in nextProps) {
    if (!nextProps.hasOwnProperty(propKey)) {
      continue;
    }
    const nextProp = nextProps[propKey];
    if (propKey === STYLE) {
      // 設置 style 屬性
      // Relies on `updateStylesByID` not mutating `styleUpdates`.
      CSSPropertyOperations.setValueForStyles(domElement, nextProp);
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      const nextHtml = nextProp ? nextProp[HTML] : undefined;
      if (nextHtml != null) {
        setInnerHTML(domElement, nextHtml);
      }
    } else if (propKey === CHILDREN) {
      if (typeof nextProp === 'string') {
        // Avoid setting initial textContent when the text is empty. In IE11 setting
        // textContent on a <textarea> will cause the placeholder to not
        // show within the <textarea> until it has been focused and blurred again.
        // https://github.com/facebook/react/issues/6731#issuecomment-254874553
        const canSetTextContent = tag !== 'textarea' || nextProp !== '';
        if (canSetTextContent) {
          setTextContent(domElement, nextProp);
        }
      } else if (typeof nextProp === 'number') {
        setTextContent(domElement, '' + nextProp);
      }
    } else if (
      propKey === SUPPRESS_CONTENT_EDITABLE_WARNING ||
      propKey === SUPPRESS_HYDRATION_WARNING
    ) {
      // Noop
    } else if (propKey === AUTOFOCUS) {
      // We polyfill it separately on the client during commit.
      // We could have excluded it in the property list instead of
      // adding a special case here, but then it wouldn't be emitted
      // on server rendering (but we *do* want to emit it in SSR).
    } else if (registrationNameModules.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        if (__DEV__ && typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }
        ensureListeningTo(rootContainerElement, propKey);
      }
    } else if (nextProp != null) {
      DOMPropertyOperations.setValueForProperty(
        domElement,
        propKey,
        nextProp,
        isCustomComponentTag,
      );
    }
  }
}

比如設置 style 樣式屬性 setValueForStyles
分為 -- 開頭的自定義和常規(guī) css 屬性来氧,就是直接操作原生 dom 上的 style诫给。

/**
 * Sets the value for multiple styles on a node.  If a value is specified as
 * '' (empty string), the corresponding style property will be unset.
 *
 * @param {DOMElement} node
 * @param {object} styles
 */
export function setValueForStyles(node, styles) {
  const style = node.style;
  for (let styleName in styles) {
    if (!styles.hasOwnProperty(styleName)) {
      continue;
    }
    const isCustomProperty = styleName.indexOf('--') === 0;
    // 嘗試為number自動補全px
    const styleValue = dangerousStyleValue(
      styleName,
      styles[styleName],
      isCustomProperty,
    );
    if (styleName === 'float') {
      styleName = 'cssFloat';
    }
    if (isCustomProperty) {
      style.setProperty(styleName, styleValue);
    } else {
      style[styleName] = styleValue;
    }
  }
}

值得一提的是,會為一些缺省的 number 數(shù)值會自動補全 'px' 單位(顯然還有好些例外的 css 屬性本身的值就是純 number):

function dangerousStyleValue(name, value, isCustomProperty) {
  const isEmpty = value == null || typeof value === 'boolean' || value === '';
  if (isEmpty) {
    return '';
  }

  if (
    !isCustomProperty &&
    typeof value === 'number' &&
    value !== 0 &&
    !(isUnitlessNumber.hasOwnProperty(name) && isUnitlessNumber[name]) // 
  ) {
    return value + 'px'; // Presumes implicit 'px' suffix for unitless numbers
  }

  return ('' + value).trim();
}

這份列表就是剛才說的“例外”啦扬,它們的樣式值就是純 number 類型中狂,沒有 px:

/**
 * CSS properties which accept numbers but are not in units of "px".
 */
export const isUnitlessNumber = {
  animationIterationCount: true,
  borderImageOutset: true,
  borderImageSlice: true,
  borderImageWidth: true,
  boxFlex: true,
  boxFlexGroup: true,
  boxOrdinalGroup: true,
  columnCount: true,
  columns: true,
  flex: true,
  flexGrow: true,
  flexPositive: true,
  flexShrink: true,
  flexNegative: true,
  flexOrder: true,
  gridArea: true,
  gridRow: true,
  gridRowEnd: true,
  gridRowSpan: true,
  gridRowStart: true,
  gridColumn: true,
  gridColumnEnd: true,
  gridColumnSpan: true,
  gridColumnStart: true,
  fontWeight: true,
  lineClamp: true,
  lineHeight: true,
  opacity: true,
  order: true,
  orphans: true,
  tabSize: true,
  widows: true,
  zIndex: true,
  zoom: true,

  // SVG-related properties
  fillOpacity: true,
  floodOpacity: true,
  stopOpacity: true,
  strokeDasharray: true,
  strokeDashoffset: true,
  strokeMiterlimit: true,
  strokeOpacity: true,
  strokeWidth: true,
};

**
更新時(進行 diff 判斷)

對于 input、select扑毡、textarea 就算 props 沒變吃型,也要 markUPdate(workInProgress) 標記后續(xù)需要更新

for 循環(huán)遍歷 lastProps 的 propKey,如果本次不是刪除該 props僚楞,則繼續(xù)遍歷勤晚,否則進行后面的刪除操作。
通過 prop 來判斷相關(guān) dom attribute 是否發(fā)生變化:

  • 刪除 style 相關(guān)屬性:把 dom style 相關(guān) css 屬性設置為 '' 空字符串即可泉褐。
  • 其他 props 的若要刪除:設置為 null赐写。
  • 對于 children,只檢查 string 或者 number 類型的 props 值是否變化膜赃,從而能否跳過更新挺邀。其他則一律更新。
  • 經(jīng)過以上判斷對比跳座,可能成功地跳過了部分不必要的 props 的更新端铛。而剩余的所有情況,則一律更新:

updatePayload.push(propKey, nextProp);

  • 然后返回 updatePalyload 數(shù)組疲眷,通過 markUpdate(workInprogress) 進行標記禾蚕。
updateHostComponent(
  current,
  workInProgress,
  type,
  newProps,
  rootContainerInstance,
);

if (current.ref !== workInProgress.ref) {
  markRef(workInProgress);
}

updateHostComponent:

  • 如果當前 fiber 節(jié)點新老 props 沒變,則跳過更新狂丝。TODO 未完待續(xù)换淆,涉及 dom diff or props diff哗总?
  updateHostComponent = function(
    current: Fiber,
    workInProgress: Fiber,
    type: Type,
    newProps: Props,
    rootContainerInstance: Container,
  ) {
    const oldProps = current.memoizedProps;
    // 跳過dom更新
    if (oldProps === newProps) {
      // In mutation mode, this is sufficient for a bailout because
      // we won't touch this node even if children changed.
      return;
    }

    const instance: Instance = workInProgress.stateNode; // 拿到dom實例
    const currentHostContext = getHostContext();
    const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // TODO: Type this specific to this type of component.
    workInProgress.updateQueue = (updatePayload: any);
    // If the update payload indicates that there is a change or if there
    // is a new ref we mark this as an update. All the work is done in commitWork.
    if (updatePayload) {
      markUpdate(workInProgress);
    }
  };

5. HostText

HostText 中涉及到 updateHostText,因為是 dom text 節(jié)點倍试,所以只需對比新老 text 字符串讯屈。

首次渲染
調(diào)用 createTextInstance,其中調(diào)用 createTextNode县习,里面其實就是在調(diào)用原生的 document.createTextNode(text)涮母,另外想要找到 document 節(jié)點,只需在任意原生 dom 節(jié)點上查詢 ownerDocument 屬性即可躁愿。

precacheFiberNode HostComponent 講過了叛本?我怎么沒印象。

6. renderRoot 中的錯誤處理

  • 給報錯節(jié)點增加 Incomplete 副作用
  • 給父鏈上具有 error boundary 的節(jié)點增加副作用攘已,收集錯誤以及進行一定的處理炮赦。
  • 創(chuàng)建錯誤相關(guān)的更新怜跑。

首先样勃,在 renderRoot 中:

do {
    try {
      workLoop(isYieldy);
    } catch (thrownValue) {
      if (nextUnitOfWork === null) {
        // This is a fatal error.
        didFatal = true;
        onUncaughtError(thrownValue);
      } else {
        
        const failedUnitOfWork: Fiber = nextUnitOfWork;
        if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
          replayUnitOfWork(failedUnitOfWork, thrownValue, isYieldy);
        }

        const sourceFiber: Fiber = nextUnitOfWork;
        let returnFiber = sourceFiber.return;
        if (returnFiber === null) {
          // This is the root. The root could capture its own errors. However,
          // we don't know if it errors before or after we pushed the host
          // context. This information is needed to avoid a stack mismatch.
          // Because we're not sure, treat this as a fatal error. We could track
          // which phase it fails in, but doesn't seem worth it. At least
          // for now.
          didFatal = true;
          onUncaughtError(thrownValue);
        } else {
          throwException(
            root,
            returnFiber,
            sourceFiber,
            thrownValue,
            nextRenderExpirationTime,
          );
          nextUnitOfWork = completeUnitOfWork(sourceFiber);
          continue;
        }
      }
    }
    break;
  } while (true);

onUncaughtError:
**
對于致命錯誤,會使用 onUncaughtError 來處理:

function onUncaughtError(error: mixed) {
  invariant(
    nextFlushedRoot !== null,
    'Should be working on a root. This error is likely caused by a bug in ' +
      'React. Please file an issue.',
  );
  // Unschedule this root so we don't work on it again until there's
  // another update.
  nextFlushedRoot.expirationTime = NoWork;
  if (!hasUnhandledError) {
    hasUnhandledError = true;
    unhandledError = error;
  }
}

throwException:
**
否則使用 throwException 對捕獲到的錯誤進行處理:

  • 先嘗試處理 Suspense 中的異常性芬。
  • 否則向上尋找可以錯誤處理的 ClassComponent 組件(比如使用了 getDerivedStateFromError 峡眶、 componentDidCatch )。為該 ClassComponent 所在節(jié)點增加 ShouldCapture 副作用標記植锉。 然后創(chuàng)建錯誤 update 并入隊列辫樱,以便在 commit 階段被調(diào)用。
  • 如果向上沒能找到能處理錯誤的 class component俊庇,則最終會在 HostRoot 上進行類似操作狮暑。
function throwException(
  root: FiberRoot,
  returnFiber: Fiber,
  sourceFiber: Fiber,
  value: mixed,
  renderExpirationTime: ExpirationTime,
) {
  // The source fiber did not complete.
  sourceFiber.effectTag |= Incomplete;
  // Its effect list is no longer valid.
  sourceFiber.firstEffect = sourceFiber.lastEffect = null;

  if (
    value !== null &&
    typeof value === 'object' &&
    typeof value.then === 'function'
  ) {
  // 處理 Suspense 相關(guān)合理的中間狀態(tài)帶來的異常
  }
  
  // We didn't find a boundary that could handle this type of exception. Start
  // over and traverse parent path again, this time treating the exception
  // as an error.
  renderDidError();
  value = createCapturedValue(value, sourceFiber);
  let workInProgress = returnFiber;
  do {
    switch (workInProgress.tag) {
      case HostRoot: {
        const errorInfo = value;
        workInProgress.effectTag |= ShouldCapture;
        workInProgress.expirationTime = renderExpirationTime;
        const update = createRootErrorUpdate(
          workInProgress,
          errorInfo,
          renderExpirationTime,
        );
        enqueueCapturedUpdate(workInProgress, update);
        return;
      }
      case ClassComponent:
        // Capture and retry
        const errorInfo = value;
        const ctor = workInProgress.type;
        const instance = workInProgress.stateNode;
        if (
          (workInProgress.effectTag & DidCapture) === NoEffect &&
          (typeof ctor.getDerivedStateFromError === 'function' ||
            (instance !== null &&
              typeof instance.componentDidCatch === 'function' &&
              !isAlreadyFailedLegacyErrorBoundary(instance)))
        ) {
          workInProgress.effectTag |= ShouldCapture;
          workInProgress.expirationTime = renderExpirationTime;
          // Schedule the error boundary to re-render using updated state
          const update = createClassErrorUpdate(
            workInProgress,
            errorInfo,
            renderExpirationTime,
          );
          enqueueCapturedUpdate(workInProgress, update);
          return;
        }
        break;
      default:
        break;
    }
    workInProgress = workInProgress.return;
  } while (workInProgress !== null);
}

** nextUnitOfWork = completeUnitOfWork(sourceFiber) :**
**
如果某個節(jié)點產(chǎn)生了錯誤,那么處理完錯誤相關(guān)的之后辉饱,會調(diào)用 completeUnitOfwork 做收尾工作搬男,向上遍歷。不會再往下遍歷渲染子節(jié)點彭沼,因為已經(jīng)報錯了缔逛,向下的局部子樹沒意義了。

7. unwindWork

  • 類似于 completeWork 對不同類型組件進行處理(其整個過程是跟 completeWork 區(qū)分開的)姓惑。
  • 對于 ShouldCapture 組件設置 DidCapture 副作用

unwindWork 和 completeWork 最大的區(qū)別是前者會判斷有 ShouldCapture 褐奴,然后取消設置,并新增 DidCapture 于毙,然后 return敦冬。

如果一個節(jié)點報錯了,對于父組件 completeUnitOfWork 時會全部走 unwindWork 而不是 completeWork唯沮。
會全部從跟組件重走一遍更新過程匪补,即 workLoop 那一套向下遍歷伞辛。

TODO 待深究。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夯缺,一起剝皮案震驚了整個濱河市蚤氏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌踊兜,老刑警劉巖竿滨,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捏境,居然都是意外死亡于游,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進店門垫言,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贰剥,“玉大人,你說我怎么就攤上這事筷频“龀桑” “怎么了?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵凛捏,是天一觀的道長担忧。 經(jīng)常有香客問我,道長坯癣,這世上最難降的妖魔是什么瓶盛? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮示罗,結(jié)果婚禮上惩猫,老公的妹妹穿的比我還像新娘。我一直安慰自己蚜点,他們只是感情好轧房,可當我...
    茶點故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著禽额,像睡著了一般锯厢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脯倒,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天实辑,我揣著相機與錄音,去河邊找鬼藻丢。 笑死剪撬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的悠反。 我是一名探鬼主播残黑,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼馍佑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了梨水?” 一聲冷哼從身側(cè)響起拭荤,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疫诽,沒想到半個月后舅世,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡奇徒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年雏亚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片摩钙。...
    茶點故事閱讀 38,599評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡罢低,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出胖笛,到底是詐尸還是另有隱情网持,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布匀钧,位于F島的核電站翎碑,受9級特大地震影響谬返,放射性物質(zhì)發(fā)生泄漏之斯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一遣铝、第九天 我趴在偏房一處隱蔽的房頂上張望佑刷。 院中可真熱鬧,春花似錦酿炸、人聲如沸瘫絮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽麦萤。三九已至,卻和暖如春扁眯,著一層夾襖步出監(jiān)牢的瞬間壮莹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工姻檀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留命满,地道東北人。 一個月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓绣版,卻偏偏與公主長得像胶台,于是被迫代替她去往敵國和親歼疮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,465評論 2 348