卷毛小狗 發表於 2019-8-1 11:33:00

react 16 渲染整理

<h1 id="背景">背景</h1>
<p>老的react架构在渲染时会有一些性能问题,从setstate到render,程序一直在跑,一直到render完成。才能继续下一步操作。如果组件比较多,或者有复杂的计算逻辑,这之间的消耗的时间是比较多的。<br>
假设更新一个组件需要1ms,如果有200个组件要更新,那就需要200ms,这200ms之间是不能响应的。如果这时候用户在input框输入什么东西,表现出来的就是明显的卡顿。<br>
React这样的调度策略对动画的支持也不好。如果React更新一次状态,占用浏览器主线程的时间超过16.6ms,就会被人眼发觉前后两帧不连续,呈现出动画卡顿。</p>
<h1 id="fiber">Fiber</h1>
<p>react团队经过两年的工作,重写了react中核心算法reconciliation。并在v16版本中发布了这个新的特性。为了区别之前和之后的reconciler,通常将之前的reconciler称为stack reconciler,重写后的称为fiber reconciler,简称为Fiber。</p>
<h1 id="区别">区别</h1>
<p>最大的变化就是支持了任务帧,把各个任务都增加了优先级,同步和异步。比如用户输入input是优先级比较高的,它可以打断低优先级的任务。<br>
比如再处理dom diff的时候耗时严重,fiber任务处理大概会有50ms的帧时长,超过这个时间就会先去看看有没高优任务去做。然后回来做低优先级任务。</p>
<ul>
<li>优先级高的任务可以中断低优先级的任务。</li>
<li>还增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行。(不过用户操作默认是同步的,暂时还没开放这个特性)</li>
<li>dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行而设计的。</li>
</ul>
<h1 id="渲染流程">渲染流程</h1>
<p><img src="https://img2018.cnblogs.com/blog/842714/201908/842714-20190801115413065-1439495851.jpg"><br>
scheduleWork - requestWork - 同步/异步 - performSyncWork- performWork - performWorkOnRoot -<br>
renderRoot/completeRoot - workLoop-performUnitOfWork-beginWork/completeUnitOfWork -updateClassComponent-reconcileChildrenAtExpirationTime- reconcileChildFibers-reconcileChildrenArray<br>
源码基于react v16.3.0 (8e3d94ff)</p>
<h2 id="setstate">setstate</h2>
<pre><code class="language-js">Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
</code></pre>
<h3 id="enqueuesetstate">enqueueSetState</h3>
<p>主要是把任务插入fiber的update queue,然后调度任务</p>
<pre><code class="language-js">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);
},
</code></pre>
<h3 id="insertupdateintofiber">insertUpdateIntoFiber</h3>
<p>插入fiber两棵树的update queue</p>
<blockquote>
<p>每个react 结点都有2个fiber链表,一个叫current fiber,一个叫alternate fiber,而每个链表又对应两个updateQueue。<br>
而currentFiber.alternate = alternateFiber; alternateFiber.alternate = currentFiber。通过alternate属性连接起来。初始化的时候,alternate fiber是current fiber 的clone。<br>
处理diff的时候,操作的是alternateFiber,处理完diff,让currentFiber = alternateFiber;这样一个处理就完成了。</p>
</blockquote>
<h3 id="schedulework">scheduleWork</h3>
<p>scheduleWork会更新每个节点的优先级,然后循环到root,以后的操作都从root开始遍历。</p>
<ul>
<li>expirationTime 优先级 expirationTime 不为 1 的时候,则其值越低,优先级越高。</li>
</ul>
<pre><code class="language-js">{
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.
};
</code></pre>
<pre><code class="language-js">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 &gt; expirationTime
      ) {
      node.expirationTime = expirationTime;
      }
      if (node.alternate !== null) {
      if (
          node.alternate.expirationTime === NoWork ||
          node.alternate.expirationTime &gt; expirationTime
      ) {
          node.alternate.expirationTime = expirationTime;
      }
      }
      if (node.return === null) {
      if (node.tag === HostRoot) {
          const root: FiberRoot = (node.stateNode: any);
          if (
            !isWorking &amp;&amp;
            nextRenderExpirationTime !== NoWork &amp;&amp;
            expirationTime &lt; 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;
    }
}

</code></pre>
<h3 id="requestwork">requestWork</h3>
<p>同步执行performSyncWork,异步执行scheduleCallbackWithExpiration,<br>
scheduleCallbackWithExpiration会调浏览器的requestidlecallback,在浏览器空闲的时候进行处理。<br>
react还对这个api做了polyfill</p>
<pre><code class="language-js">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);
    }
}
</code></pre>
<h3 id="performsyncwork-主要的任务调度">performSyncWork 主要的任务调度</h3>
<p>这里会找到高优任务先执行。<br>
同步任务会直接调用performWorkOnRoot进行下一步,<br>
异步任务也会调performWorkOnRoot,但处理不太一样<br>
如果有上次遗留的任务,留到空闲时运行</p>
<pre><code class="language-js">function performSyncWork() {
    performWork(Sync, false, null);
}

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

    findHighestPriorityRoot();

    if (isAsync) {
      while (
      nextFlushedRoot !== null &amp;&amp;
      nextFlushedExpirationTime !== NoWork &amp;&amp;
      (minExpirationTime === NoWork ||
          minExpirationTime &gt;= nextFlushedExpirationTime) &amp;&amp;
      (!deadlineDidExpire ||
          recalculateCurrentTime() &gt;= nextFlushedExpirationTime)
      ) {
      performWorkOnRoot(
          nextFlushedRoot,
          nextFlushedExpirationTime,
          !deadlineDidExpire,
      );
      findHighestPriorityRoot();
      }
    } else {
      while (
      nextFlushedRoot !== null &amp;&amp;
      nextFlushedExpirationTime !== NoWork &amp;&amp;
      (minExpirationTime === NoWork ||
          minExpirationTime &gt;= 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();
}
</code></pre>
<h3 id="performworkonroot-异步任务和同步任务的异同">performWorkOnRoot (异步任务和同步任务的异同)</h3>
<p>如果有上次遗留,直接调用completeRoot进到渲染阶段。如果没有就调renderRoot开始reconcilation阶段。<br>
异步任务主要是渲染的时候判断一下时间,如果没时间了,先把finishedWork赋给全局,下次循环处理。</p>
<pre><code class="language-js">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;
}
</code></pre>
<h3 id="renderroot">renderRoot</h3>
<p>如果是第一次进入,会创建一个nextUnitOfWork。<br>
nextUnitOfWork是每个工作的粒度。<br>
然后调用workLoop</p>
<pre><code class="language-js">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.
    // ...
}
</code></pre>
<h3 id="workloop">workLoop</h3>
<p>异步任务在处理的时候会调用shouldYield,shouldYield会判断是不是已经超时了,超时暂时先不做。</p>
<pre><code class="language-js">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 &amp;&amp; !shouldYield()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
      }
    }
}
function shouldYield() {
    if (deadline === null) {
      return false;
    }
    if (deadline.timeRemaining() &gt; 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;
}
</code></pre>
<h3 id="performunitofwork-reconcilation阶段">performUnitOfWork (reconcilation阶段)</h3>
<p>reconcilation又分两步<br>
1是beginWork,beginWork会开始处理组件,针对不同组件不同处理。包括dom diff<br>
2 是completeUnitOfWork,completeUnitOfWork会对begin work产生的effect list进行一些处理。</p>
<pre><code class="language-js"> 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;
}

</code></pre>
<h3 id="beginwork">beginWork</h3>
<p>主要是对react 组件进行一些操作。和调用一些生命周期,<br>
我们主要关注classComponent,就是react的组件<br>
HostConponent在浏览器下就是dom</p>
<pre><code class="language-js">function beginWork(
    current: Fiber | null,
    workInProgress: Fiber,
    renderExpirationTime: ExpirationTime,
): Fiber | null {
    if (
      workInProgress.expirationTime === NoWork ||
      workInProgress.expirationTime &gt; 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.',
      );
    }
}
</code></pre>
<h3 id="updateclasscomponent">updateClassComponent</h3>
<p>mount组件,构建组件实例,调用生命周期比如willMount,初始化组件的的updateQueue。</p>
<ul>
<li>updateClassInstance中,如果props不一致,会调willReceiveProps方法,然后checkShouldCompoentUpdate,也就是<br>
shouldCompoentUpdate。</li>
<li>finishClassComponent中,会判断之前的shouldUpdate,如果是true就要调用组件的render,产出children,然后对children进行dom diff。</li>
</ul>
<pre><code class="language-js"> 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 &amp;&amp; updateQueue.capturedValues !== null) {
      shouldUpdate = true;
      didCaptureError = true;
    }
    return finishClassComponent(
      current,
      workInProgress,
      shouldUpdate,
      hasContext,
      didCaptureError,
      renderExpirationTime,
    );
}
</code></pre>
<h3 id="reconcilechildfibers-virtul-dom-diff">reconcileChildFibers (virtul dom diff)</h3>
<p>finishClassComponent会调用reconcileChildFibers进行dom diff。</p>
<pre><code class="language-js"> function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
): Fiber | null {
    if (
      typeof newChild === 'object' &amp;&amp;
      newChild !== null &amp;&amp;
      newChild.type === REACT_FRAGMENT_TYPE &amp;&amp;
      newChild.key === null
    ) {
      newChild = newChild.props.children;
    }

    // Handle object types
    const isObject = typeof newChild === 'object' &amp;&amp; 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,
      );
    }
}
</code></pre>
<h3 id="reconcilechildrenarray">reconcileChildrenArray</h3>
<p>大部分情况是reconcileChildrenArray,就那这个来说。</p>
<pre><code class="language-javascript">function reconcileChildrenArray(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChildren: Array&lt;*&gt;,
    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 &amp;&amp; newIdx &lt; newChildren.length; newIdx++) {
   // 没有采用两端同时对比,受限于Fiber列表的单向结构
      if (oldFiber.index &gt; newIdx) {
      nextOldFiber = oldFiber;
      oldFiber = null;
      } else {
      nextOldFiber = oldFiber.sibling;
      }
      const newFiber = updateSlot( // 生成新的fiber
      returnFiber,
      oldFiber,
      newChildren,
      expirationTime,
      );
      //如果在遍历中发现key值不相等的情况,则直接跳出第一轮遍历
      if (newFiber === null) {
      if (oldFiber === null) {
          oldFiber = nextOldFiber;
      }
      break;
      }
      if (shouldTrackSideEffects) {
      if (oldFiber &amp;&amp; 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 &lt; newChildren.length; newIdx++) {
      const newFiber = createChild(
          returnFiber,
          newChildren,
          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 &lt; newChildren.length; newIdx++) {
      const newFiber = updateFromMap(
      existingChildren,
      returnFiber,
      newIdx,
      newChildren,
      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 =&gt; deleteChild(returnFiber, child));
    }
    // 最终返回Fiber子节点列表的第一个节点
    return resultingFirstChild;
}
</code></pre>
<p>可以看到其实删除节点并不是直接删除而是打个Deletion的tag。生成effect list</p>
<pre><code class="language-js">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;
}
</code></pre>
<h3 id="completeunitofwork">completeUnitOfWork</h3>
<p>在dom diff之后会有一个收尾工作大概就是effect的各种处理,就是workLoop之后的completeUnitOfWork函数。<br>
同步effect list到 current 的host root 树。<br>
调用completeWork</p>
<pre><code class="language-js">function completeUnitOfWork(workInProgress: Fiber): Fiber | null {
    while (true) {
      const current = workInProgress.alternate;

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

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

      if (next !== null) {
          stopWorkTimer(workInProgress);
          if (__DEV__ &amp;&amp; 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 &amp;&amp;
          // Do not append effects to parents if a sibling failed to complete
          (returnFiber.effectTag &amp; 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 &gt; 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 &amp; DidCapture) {
          // Restarting an error boundary
          stopFailedWorkTimer(workInProgress);
      } else {
          stopWorkTimer(workInProgress);
      }
      if (next !== null) {
          stopWorkTimer(workInProgress);
          if (__DEV__ &amp;&amp; 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 &amp;= 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;
}
</code></pre>
<h3 id="completework">completeWork</h3>
<p>比较长,不贴代码了。主要做的事情就是根据不同的component类型进行不同的处理。<br>
重点是对HostComponent的props进行diff,并标记更新。<br>
如果是react首次渲染,调用createInstance创建一个HostComponent。<br>
如果已经存在HostComponent,检查节点是否需要更新,调用prepareUpdate,进行diff dom属性。</p>
<blockquote>
<p>到此reconciliation阶段结束,主要负责产出effect list。<br>
可以说reconcile的过程相当于是一个纯函数,输入是fiber节点,输出一个effect list。</p>
</blockquote>
<blockquote>
<p>因为纯函数的可预测性,让我们可以随时中断reconciliation阶段的执行,而不用担心side-effects给让组件状态和实际UI产生不一致</p>
</blockquote>
<h3 id="渲染阶段-completerootcommitroot">渲染阶段 completeRoot/commitRoot</h3>
<pre><code class="language-js">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 &gt; 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;
}
</code></pre><br><br>
来源:https://www.cnblogs.com/dh-dh/p/11281479.html
頁: [1]
查看完整版本: react 16 渲染整理