react 16 渲染整理

背景

老的react架構在渲染時會有一些性能問題,從setstate到render,程序一直在跑,一直到render完成。才能繼續下一步操做。若是組件比較多,或者有複雜的計算邏輯,這之間的消耗的時間是比較多的。 假設更新一個組件須要1ms,若是有200個組件要更新,那就須要200ms,這200ms之間是不能響應的。若是這時候用戶在input框輸入什麼東西,表現出來的就是明顯的卡頓。 React這樣的調度策略對動畫的支持也很差。若是React更新一次狀態,佔用瀏覽器主線程的時間超過16.6ms,就會被人眼發覺先後兩幀不連續,呈現出動畫卡頓。javascript

Fiber

react團隊通過兩年的工做,重寫了react中核心算法reconciliation。並在v16版本中發佈了這個新的特性。爲了區別以前和以後的reconciler,一般將以前的reconciler稱爲stack reconciler,重寫後的稱爲fiber reconciler,簡稱爲Fiber。java

區別

最大的變化就是支持了任務幀,把各個任務都增長了優先級,同步和異步。好比用戶輸入input是優先級比較高的,它能夠打斷低優先級的任務。 好比再處理dom diff的時候耗時嚴重,fiber任務處理大概會有50ms的幀時長,超過這個時間就會先去看看有沒高優任務去作。而後回來作低優先級任務。node

  • 優先級高的任務能夠中斷低優先級的任務。
  • 還增長了異步任務,調用requestIdleCallback api,瀏覽器空閒的時候執行。(不過用戶操做默認是同步的,暫時還沒開放這個特性)
  • dom diff樹變成了鏈表,一個dom對應兩個fiber(一個鏈表),對應兩個隊列,這都是爲找到被中斷的任務,從新執行而設計的。

渲染流程

scheduleWork - requestWork - 同步/異步 - performSyncWork- performWork - performWorkOnRoot - renderRoot/completeRoot - workLoop-performUnitOfWork-beginWork/completeUnitOfWork -updateClassComponent-reconcileChildrenAtExpirationTime- reconcileChildFibers-reconcileChildrenArray 源碼基於react v16.3.0 (8e3d94ff)react

setstate

Component.prototype.setState = function(partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

enqueueSetState

主要是把任務插入fiber的update queue,而後調度任務算法

enqueueSetState(instance, partialState, callback) {
    const fiber = ReactInstanceMap.get(instance);
    callback = callback === undefined ? null : callback;

    const expirationTime = computeExpirationForFiber(fiber);
    const update = {
        expirationTime,
        partialState,
        callback,
        isReplace: false,
        isForced: false,
        capturedValue: null,
        next: null,
    };
    insertUpdateIntoFiber(fiber, update);
    scheduleWork(fiber, expirationTime);
},

insertUpdateIntoFiber

插入fiber兩棵樹的update queueapi

每一個react 結點都有2個fiber鏈表,一個叫current fiber,一個叫alternate fiber,而每一個鏈表又對應兩個updateQueue。 而currentFiber.alternate = alternateFiber; alternateFiber.alternate = currentFiber。經過alternate屬性鏈接起來。初始化的時候,alternate fiber是current fiber 的clone。 處理diff的時候,操做的是alternateFiber,處理完diff,讓currentFiber = alternateFiber;這樣一個處理就完成了。瀏覽器

scheduleWork

scheduleWork會更新每一個節點的優先級,而後循環到root,之後的操做都從root開始遍歷。架構

  • expirationTime 優先級 expirationTime 不爲 1 的時候,則其值越低,優先級越高。
{
  NoWork: 0,              // No work is pending.
  SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
  AnimationPriority: 2,   // Needs to complete before the next frame.
  HighPriority: 3,        // Interaction that needs to complete pretty soon to feel responsive.
  LowPriority: 4,         // Data fetching, or result from updating stores.
  OffscreenPriority: 5,   // Won't be visible but do the work in case it becomes visible.
};
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
    return scheduleWorkImpl(fiber, expirationTime, false);
}
function scheduleWorkImpl(
    fiber: Fiber,
    expirationTime: ExpirationTime,
    isErrorRecovery: boolean,
  ) {
    recordScheduleUpdate(); // 記錄更新,實際啥也沒幹

    let node = fiber;
    while (node !== null) {
      // Walk the parent path to the root and update each node's
      // expiration time.
      // 更新每一個node的優先級
      if (
        node.expirationTime === NoWork ||
        node.expirationTime > expirationTime
      ) {
        node.expirationTime = expirationTime;
      }
      if (node.alternate !== null) {
        if (
          node.alternate.expirationTime === NoWork ||
          node.alternate.expirationTime > expirationTime
        ) {
          node.alternate.expirationTime = expirationTime;
        }
      }
      if (node.return === null) {
        if (node.tag === HostRoot) {
          const root: FiberRoot = (node.stateNode: any);
          if (
            !isWorking &&
            nextRenderExpirationTime !== NoWork &&
            expirationTime < nextRenderExpirationTime
          ) {
            // This is an interruption. (Used for performance tracking.)
            interruptedBy = fiber;
            resetStack();
          }
          if (
            // If we're in the render phase, we don't need to schedule this root
            // for an update, because we'll do it before we exit...
            !isWorking ||
            isCommitting ||
            // ...unless this is a different root than the one we're rendering.
            nextRoot !== root
          ) {
            // Add this root to the root schedule.
            requestWork(root, expirationTime);
          }
        } else {
          }
          return;
        }
      }
      node = node.return;
    }
  }

requestWork

同步執行performSyncWork,異步執行scheduleCallbackWithExpiration, scheduleCallbackWithExpiration會調瀏覽器的requestidlecallback,在瀏覽器空閒的時候進行處理。 react還對這個api作了polyfillapp

function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
    if (isRendering) {
      return;
    }
    if (isBatchingUpdates) { // 這裏是BatchingUpdates的處理。
      // Flush work at the end of the batch.
      if (isUnbatchingUpdates) {
        // ...unless we're inside unbatchedUpdates, in which case we should
        // flush it now.
        nextFlushedRoot = root;
        nextFlushedExpirationTime = Sync;
        performWorkOnRoot(root, Sync, false);
      }
      return;
    }
    if (expirationTime === Sync) {
      performSyncWork();
    } else {
      scheduleCallbackWithExpiration(expirationTime);
    }
  }

performSyncWork 主要的任務調度

這裏會找到高優任務先執行。 同步任務會直接調用performWorkOnRoot進行下一步, 異步任務也會調performWorkOnRoot,但處理不太同樣 若是有上次遺留的任務,留到空閒時運行less

function performSyncWork() {
    performWork(Sync, false, null);
  }

  function performWork(
    minExpirationTime: ExpirationTime,
    isAsync: boolean,
    dl: Deadline | null,
  ) {
    deadline = dl;

    findHighestPriorityRoot();

    if (isAsync) {
      while (
        nextFlushedRoot !== null &&
        nextFlushedExpirationTime !== NoWork &&
        (minExpirationTime === NoWork ||
          minExpirationTime >= nextFlushedExpirationTime) &&
        (!deadlineDidExpire ||
          recalculateCurrentTime() >= nextFlushedExpirationTime)
      ) {
        performWorkOnRoot(
          nextFlushedRoot,
          nextFlushedExpirationTime,
          !deadlineDidExpire,
        );
        findHighestPriorityRoot();
      }
    } else {
      while (
        nextFlushedRoot !== null &&
        nextFlushedExpirationTime !== NoWork &&
        (minExpirationTime === NoWork ||
          minExpirationTime >= nextFlushedExpirationTime)
      ) {
        performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
        findHighestPriorityRoot();
      }
    }

    if (deadline !== null) {
      callbackExpirationTime = NoWork;
      callbackID = -1;
    }
    // If there's work left over, schedule a new callback.
    if (nextFlushedExpirationTime !== NoWork) {
      scheduleCallbackWithExpiration(nextFlushedExpirationTime);
    }

    // Clean-up.
    deadline = null;
    deadlineDidExpire = false;

    finishRendering();
  }

performWorkOnRoot (異步任務和同步任務的異同)

若是有上次遺留,直接調用completeRoot進到渲染階段。若是沒有就調renderRoot開始reconcilation階段。 異步任務主要是渲染的時候判斷一下時間,若是沒時間了,先把finishedWork賦給全局,下次循環處理。

function performWorkOnRoot(
    root: FiberRoot,
    expirationTime: ExpirationTime,
    isAsync: boolean,
  ) {
    isRendering = true;

    // Check if this is async work or sync/expired work.
    if (!isAsync) {
      // Flush sync work.
      let finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // This root is already complete. We can commit it.
        completeRoot(root, finishedWork, expirationTime);
      } else {
        root.finishedWork = null;
        finishedWork = renderRoot(root, expirationTime, false);
        if (finishedWork !== null) {
          // We've completed the root. Commit it.
          completeRoot(root, finishedWork, expirationTime);
        }
      }
    } else {
      // Flush async work.
      let finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // This root is already complete. We can commit it.
        completeRoot(root, finishedWork, expirationTime);
      } else {
        root.finishedWork = null;
        finishedWork = renderRoot(root, expirationTime, true);
        if (finishedWork !== null) {
          // We've completed the root. Check the deadline one more time
          // before committing.
          if (!shouldYield()) {
            // Still time left. Commit the root.
            completeRoot(root, finishedWork, expirationTime);
          } else {
            // There's no time left. Mark this root as complete. We'll come
            // back and commit it later.
            root.finishedWork = finishedWork;
          }
        }
      }
    }

    isRendering = false;
  }

renderRoot

若是是第一次進入,會建立一個nextUnitOfWork。 nextUnitOfWork是每一個工做的粒度。 而後調用workLoop

function renderRoot(
    root: FiberRoot,
    expirationTime: ExpirationTime,
    isAsync: boolean,
  ): Fiber | null {
    isWorking = true;

    // Check if we're starting from a fresh stack, or if we're resuming from
    // previously yielded work.
    if (
      expirationTime !== nextRenderExpirationTime ||
      root !== nextRoot ||
      nextUnitOfWork === null
    ) {
      // Reset the stack and start working from the root.
      resetStack();
      nextRoot = root;
      nextRenderExpirationTime = expirationTime;
      nextUnitOfWork = createWorkInProgress(
        nextRoot.current,
        null,
        nextRenderExpirationTime,
      );
      root.pendingCommitExpirationTime = NoWork;
    }

    let didFatal = false;

    startWorkLoopTimer(nextUnitOfWork);

    do {
      try {
        workLoop(isAsync);
      } catch (thrownValue) {
        // ...
      }
      break;
    } while (true);

    // We're done performing work. Time to clean up.
    // ...
  }

workLoop

異步任務在處理的時候會調用shouldYield,shouldYield會判斷是否是已經超時了,超時暫時先不作。

function workLoop(isAsync) {
    if (!isAsync) {
      // Flush all expired work.
      while (nextUnitOfWork !== null) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
    } else {
      // Flush asynchronous work until the deadline runs out of time.
      while (nextUnitOfWork !== null && !shouldYield()) {
        nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
    }
  }
function shouldYield() {
    if (deadline === null) {
      return false;
    }
    if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
      // Disregard deadline.didTimeout. Only expired work should be flushed
      // during a timeout. This path is only hit for non-expired work.
      return false;
    }
    deadlineDidExpire = true;
    return true;
  }

performUnitOfWork (reconcilation階段)

reconcilation又分兩步 1是beginWork,beginWork會開始處理組件,針對不一樣組件不一樣處理。包括dom diff 2 是completeUnitOfWork,completeUnitOfWork會對begin work產生的effect list進行一些處理。

function performUnitOfWork(workInProgress: Fiber): Fiber | null {
    const current = workInProgress.alternate;
    startWorkTimer(workInProgress);
    let next = beginWork(current, workInProgress, nextRenderExpirationTime);

    if (next === null) {
      next = completeUnitOfWork(workInProgress);
    }

    ReactCurrentOwner.current = null;
    return next;
  }

beginWork

主要是對react 組件進行一些操做。和調用一些生命週期, 咱們主要關注classComponent,就是react的組件 HostConponent在瀏覽器下就是dom

function beginWork(
    current: Fiber | null,
    workInProgress: Fiber,
    renderExpirationTime: ExpirationTime,
  ): Fiber | null {
    if (
      workInProgress.expirationTime === NoWork ||
      workInProgress.expirationTime > renderExpirationTime
    ) {
      return bailoutOnLowPriority(current, workInProgress);
    }

    switch (workInProgress.tag) {
      case FunctionalComponent:
        return updateFunctionalComponent(current, workInProgress);
      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 ForwardRef:
        return updateForwardRef(current, workInProgress);
      case Fragment:
        return updateFragment(current, workInProgress);
      case Mode:
        return updateMode(current, workInProgress);
      case ContextProvider:
        return updateContextProvider(
          current,
          workInProgress,
          renderExpirationTime,
        );
      case ContextConsumer:
        return updateContextConsumer(
          current,
          workInProgress,
          renderExpirationTime,
        );
      default:
        invariant(
          false,
          'Unknown unit of work tag. This error is likely caused by a bug in ' +
            'React. Please file an issue.',
        );
    }
  }

updateClassComponent

mount組件,構建組件實例,調用生命週期好比willMount,初始化組件的的updateQueue。

  • updateClassInstance中,若是props不一致,會調willReceiveProps方法,而後checkShouldCompoentUpdate,也就是 shouldCompoentUpdate。
  • finishClassComponent中,會判斷以前的shouldUpdate,若是是true就要調用組件的render,產出children,而後對children進行dom diff。
function updateClassComponent(
    current: Fiber | null,
    workInProgress: Fiber,
    renderExpirationTime: ExpirationTime,
  ) {
    // Push context providers early to prevent context stack mismatches.
    // During mounting we don't know the child context yet as the instance doesn't exist.
    // We will invalidate the child context in finishClassComponent() right after rendering.
    const hasContext = pushLegacyContextProvider(workInProgress);
    let shouldUpdate;
    if (current === null) {
      if (workInProgress.stateNode === null) {
        // In the initial pass we might need to construct the instance.
        constructClassInstance(workInProgress, workInProgress.pendingProps);
        mountClassInstance(workInProgress, renderExpirationTime);

        shouldUpdate = true;
      } else {
        // In a resume, we'll already have an instance we can reuse.
        shouldUpdate = resumeMountClassInstance(
          workInProgress,
          renderExpirationTime,
        );
      }
    } else {
      shouldUpdate = updateClassInstance(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }

    let didCaptureError = false;
    const updateQueue = workInProgress.updateQueue;
    if (updateQueue !== null && updateQueue.capturedValues !== null) {
      shouldUpdate = true;
      didCaptureError = true;
    }
    return finishClassComponent(
      current,
      workInProgress,
      shouldUpdate,
      hasContext,
      didCaptureError,
      renderExpirationTime,
    );
  }

reconcileChildFibers (virtul dom diff)

finishClassComponent會調用reconcileChildFibers進行dom diff。

function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    if (
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null
    ) {
      newChild = newChild.props.children;
    }

    // Handle object types
    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,
      );
    }
  }

reconcileChildrenArray

大部分狀況是reconcileChildrenArray,就那這個來講。

function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array<*>,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    let resultingFirstChild: Fiber | null = null;
    let previousNewFiber: Fiber | null = null;

    let oldFiber = currentFirstChild;
    let lastPlacedIndex = 0;
    let newIdx = 0;
    let nextOldFiber = null;
    for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
     // 沒有采用兩端同時對比,受限於Fiber列表的單向結構
      if (oldFiber.index > newIdx) {
        nextOldFiber = oldFiber;
        oldFiber = null;
      } else {
        nextOldFiber = oldFiber.sibling;
      }
      const newFiber = updateSlot( // 生成新的fiber
        returnFiber,
        oldFiber,
        newChildren[newIdx],
        expirationTime,
      );
      //若是在遍歷中發現key值不相等的狀況,則直接跳出第一輪遍歷
      if (newFiber === null) { 
        if (oldFiber === null) {
          oldFiber = nextOldFiber;
        }
        break;
      }
      if (shouldTrackSideEffects) {
        if (oldFiber && newFiber.alternate === null) {
          // 咱們找到了匹配的節點,但咱們並不保留當前的Fiber,因此咱們須要刪除當前的子節點
          // We matched the slot, but we didn't reuse the existing fiber, so we
          // need to delete the existing child.
          deleteChild(returnFiber, oldFiber);
        }
      }
      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
      // 記錄上一個更新的子節點
      if (previousNewFiber === null) {
        resultingFirstChild = newFiber;
      } else {
        previousNewFiber.sibling = newFiber;
      }
      previousNewFiber = newFiber;
      oldFiber = nextOldFiber;
    }

    if (newIdx === newChildren.length) {
      // 咱們已經遍歷完了全部的新節點,直接刪除剩餘舊節點
      // We've reached the end of the new children. We can delete the rest.
      deleteRemainingChildren(returnFiber, oldFiber);
      return resultingFirstChild;
    }

    if (oldFiber === null) {
      // 若是舊節點先遍歷完,則按順序插入剩餘的新節點
      // If we don't have any more existing children we can choose a fast path
      // since the rest will all be insertions.
      for (; newIdx < newChildren.length; newIdx++) {
        const newFiber = createChild(
          returnFiber,
          newChildren[newIdx],
          expirationTime,
        );
        if (!newFiber) {
          continue;
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          // TODO: Move out of the loop. This only happens for the first run.
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
      return resultingFirstChild;
    }

    // 把子節點都設置快速查找的map映射集
    const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

    // 使用map查找須要保存或刪除的節點
    for (; newIdx < newChildren.length; newIdx++) {
      const newFiber = updateFromMap(
        existingChildren,
        returnFiber,
        newIdx,
        newChildren[newIdx],
        expirationTime,
      );
      if (newFiber) {
        if (shouldTrackSideEffects) {
          if (newFiber.alternate !== null) {
            // 新的Fiber也是一個工做線程,可是若是已有當前的實例,那咱們就能夠複用這個Fiber,
            // 咱們要從Map中刪除這個新的,避免準備複用的Fiber被刪除
            existingChildren.delete(
              newFiber.key === null ? newIdx : newFiber.key,
            );
          }
        }
        lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
        if (previousNewFiber === null) {
          resultingFirstChild = newFiber;
        } else {
          previousNewFiber.sibling = newFiber;
        }
        previousNewFiber = newFiber;
      }
    }

    if (shouldTrackSideEffects) {
      // Any existing children that weren't consumed above were deleted. We need
      // to add them to the deletion list.
      // 到此全部剩餘的Map的節點都將被刪除,加入刪除隊列
      existingChildren.forEach(child => deleteChild(returnFiber, child));
    }
    // 最終返回Fiber子節點列表的第一個節點
    return resultingFirstChild;
  }

能夠看到其實刪除節點並非直接刪除而是打個Deletion的tag。生成effect list

function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
    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;
  }

completeUnitOfWork

在dom diff以後會有一個收尾工做大概就是effect的各類處理,就是workLoop以後的completeUnitOfWork函數。 同步effect list到 current 的host root 樹。 調用completeWork

function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
    while (true) {
      const current = workInProgress.alternate;

      const returnFiber = workInProgress.return;
      const siblingFiber = workInProgress.sibling;

      if ((workInProgress.effectTag & Incomplete) === NoEffect) {
        // This fiber completed.
        let next = completeWork(
          current,
          workInProgress,
          nextRenderExpirationTime,
        );
        stopWorkTimer(workInProgress);
        resetExpirationTime(workInProgress, nextRenderExpirationTime);

        if (next !== null) {
          stopWorkTimer(workInProgress);
          if (__DEV__ && ReactFiberInstrumentation.debugTool) {
            ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
          }
          // If completing this work spawned new work, do that next. We'll come
          // back here again.
          return next;
        }

        // 將當前fiber子樹上的effect list 插入到當前hostRoot 樹的effectlist中
        if (
          returnFiber !== null &&
          // Do not append effects to parents if a sibling failed to complete
          (returnFiber.effectTag & Incomplete) === NoEffect
        ) {
          // Append all the effects of the subtree and this fiber onto the effect
          // list of the parent. The completion order of the children affects the
          // side-effect order.
          if (returnFiber.firstEffect === null) {
            returnFiber.firstEffect = workInProgress.firstEffect;
          }
          if (workInProgress.lastEffect !== null) {
            if (returnFiber.lastEffect !== null) {
              returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
            }
            returnFiber.lastEffect = workInProgress.lastEffect;
          }

          // If this fiber had side-effects, we append it AFTER the children's
          // side-effects. We can perform certain side-effects earlier if
          // needed, by doing multiple passes over the effect list. We don't want
          // to schedule our own side-effect on our own list because if end up
          // reusing children we'll schedule this effect onto itself since we're
          // at the end.
          const effectTag = workInProgress.effectTag;
          // Skip both NoWork and PerformedWork tags when creating the effect list.
          // PerformedWork effect is read by React DevTools but shouldn't be committed.
          if (effectTag > PerformedWork) {
            if (returnFiber.lastEffect !== null) {
              returnFiber.lastEffect.nextEffect = workInProgress;
            } else {
              returnFiber.firstEffect = workInProgress;
            }
            returnFiber.lastEffect = workInProgress;
          }
        }

        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 {
          // We've reached the root.
          isRootReadyForCommit = true;
          return null;
        }
      } else {
        // This fiber did not complete because something threw. Pop values off
        // the stack without entering the complete phase. If this is a boundary,
        // capture values if possible.
        const next = unwindWork(workInProgress);
        // Because this fiber did not complete, don't reset its expiration time.
        if (workInProgress.effectTag & DidCapture) {
          // Restarting an error boundary
          stopFailedWorkTimer(workInProgress);
        } else {
          stopWorkTimer(workInProgress);
        }
        if (next !== null) {
          stopWorkTimer(workInProgress);
          if (__DEV__ && ReactFiberInstrumentation.debugTool) {
            ReactFiberInstrumentation.debugTool.onCompleteWork(workInProgress);
          }
          // If completing this work spawned new work, do that next. We'll come
          // back here again.
          // Since we're restarting, remove anything that is not a host effect
          // from the effect tag.
          next.effectTag &= HostEffectMask;
          return next;
        }

        if (returnFiber !== null) {
          // Mark the parent fiber as incomplete and clear its effect list.
          returnFiber.firstEffect = returnFiber.lastEffect = null;
          returnFiber.effectTag |= Incomplete;
        }
        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 {
          return null;
        }
      }
    }

    return null;
  }

completeWork

比較長,不貼代碼了。主要作的事情就是根據不一樣的component類型進行不一樣的處理。 重點是對HostComponent的props進行diff,並標記更新。 若是是react首次渲染,調用createInstance建立一個HostComponent。 若是已經存在HostComponent,檢查節點是否須要更新,調用prepareUpdate,進行diff dom屬性。

到此reconciliation階段結束,主要負責產出effect list。 能夠說reconcile的過程至關因而一個純函數,輸入是fiber節點,輸出一個effect list。

由於純函數的可預測性,讓咱們能夠隨時中斷reconciliation階段的執行,而不用擔憂side-effects給讓組件狀態和實際UI產生不一致

渲染階段 completeRoot/commitRoot

function commitRoot(finishedWork: Fiber): ExpirationTime {
    isWorking = true;
    isCommitting = true;
    startCommitTimer();

    const root: FiberRoot = finishedWork.stateNode;
    const committedExpirationTime = root.pendingCommitExpirationTime;
    root.pendingCommitExpirationTime = NoWork;

    const currentTime = recalculateCurrentTime();

    // Reset this to null before calling lifecycles
    ReactCurrentOwner.current = null;

    let firstEffect;
    if (finishedWork.effectTag > PerformedWork) {
      // fiber的effect list只包括它子樹中的effects,將節點的effect加到effect list鏈表中
      if (finishedWork.lastEffect !== null) {
        finishedWork.lastEffect.nextEffect = finishedWork;
        firstEffect = finishedWork.firstEffect;
      } else {
        firstEffect = finishedWork;
      }
    } else {
      // There is no effect on the root.
      firstEffect = finishedWork.firstEffect;
    }

    // 作一些dom事件相關設置
    prepareForCommit(root.containerInfo);

    // Commit all the side-effects within a tree. We'll do this in two passes.
    // The first pass performs all the host insertions, updates, deletions and
    // ref unmounts.
    nextEffect = firstEffect;
    startCommitHostEffectsTimer();
    while (nextEffect !== null) {
      let didError = false;
      let error;
        try {
             // 遍歷fiber樹的effect list,調用相關的生命週期,好比willUnmount。操做dom,完成渲染。
            commitAllHostEffects();
        } catch (e) {
            didError = true;
            error = e;
        }
    }
    stopCommitHostEffectsTimer();

    resetAfterCommit(root.containerInfo);

    root.current = finishedWork;
    nextEffect = firstEffect;
    startCommitLifeCyclesTimer();
    while (nextEffect !== null) {
      let didError = false;
      let error;
        try {
            // 再遍歷effect list,若是effect發生在classComponent上,加調didMount和didUpdate方法。
            // 若是發生在hostComponents上,會調用commitMount方法,主要是爲了在render一個節點渲染以後作一些操做。好比input的auto-focus。
            commitAllLifeCycles(root, currentTime, committedExpirationTime);
        } catch (e) {
            didError = true;
            error = e;
        }
    }

    isCommitting = false;
    isWorking = false;
    stopCommitLifeCyclesTimer();
    stopCommitTimer();
    if (typeof onCommitRoot === 'function') {
      onCommitRoot(finishedWork.stateNode);
    }

    const remainingTime = root.current.expirationTime;
    if (remainingTime === NoWork) {
      // If there's no remaining work, we can clear the set of already failed
      // error boundaries.
      legacyErrorBoundariesThatAlreadyFailed = null;
    }
    return remainingTime;
  }
相關文章
相關標籤/搜索