React16源码: React中的performWork的源码实现

发布时间:2024年01月16日

performWork


1 )概述

  • performWork 涉及到在调度完成,或者同步任务进来之后整个 root 节点链条如何更新
  • 怎么更新一棵 Fiber 树,它的每一个节点是如何被遍历到,以及如何进行更新操作
  • A. 在执行 performWork 时候,是否有 deadline 的区分
    • deadline 是通过 reactschedule 它的一个时间片,更新的过程当中
    • 产生的一个叫做 deadlineobject 的对象
    • 它可以用来判断我们在一帧的渲染时间内留给react进行fiber树渲染的时间还有没有
  • B.循环渲染root的条件
    • 一个应用当中可能会有多个root节点
    • 同时每一个root节点上面呢又会有不同优先级的任务产生
    • 要循环去遍历各个不同的root节点
    • 以及他们的不同的优先级的任务,然后按照优先级去一个个去更新
    • 这个循环它如何建立,如何判断这个循环是否成立的条件
  • C.超过时间片之后的一个处理
    • 在deadline到了之后,就是我们这一帧的渲染时间已经到了
    • 我们需要把js的执行权又交回给浏览器,这个时候又该怎么做

2 )源码

定位到 packages/react-reconciler/src/ReactFiberScheduler.js

function performAsyncWork() {
  try {
    if (!shouldYieldToRenderer()) {
      // The callback timed out. That means at least one update has expired.
      // Iterate through the root schedule. If they contain expired work, set
      // the next render expiration time to the current time. This has the effect
      // of flushing all expired work in a single batch, instead of flushing each
      // level one at a time.
      if (firstScheduledRoot !== null) {
        recomputeCurrentRendererTime();
        let root: FiberRoot = firstScheduledRoot;
        do {
          didExpireAtExpirationTime(root, currentRendererTime);
          // The root schedule is circular, so this is never null.
          root = (root.nextScheduledRoot: any);
        } while (root !== firstScheduledRoot);
      }
    }
    performWork(NoWork, true);
  } finally {
    didYield = false;
  }
}


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

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

  // Keep working on roots until there's no more work, or until we reach
  // the deadline.
  findHighestPriorityRoot();

  if (deadline !== null) {
    recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;

    if (enableUserTimingAPI) {
      const didExpire = nextFlushedExpirationTime < currentRendererTime;
      const timeout = expirationTimeToMs(nextFlushedExpirationTime);
      stopRequestCallbackTimer(didExpire, timeout);
    }

    while (
      nextFlushedRoot !== null &&
      nextFlushedExpirationTime !== NoWork &&
      (minExpirationTime === NoWork ||
        minExpirationTime >= nextFlushedExpirationTime) &&
      (!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)
    ) {
      performWorkOnRoot(
        nextFlushedRoot,
        nextFlushedExpirationTime,
        currentRendererTime >= nextFlushedExpirationTime,
      );
      findHighestPriorityRoot();
      recomputeCurrentRendererTime();
      currentSchedulerTime = currentRendererTime;
    }
  } else {
    while (
      nextFlushedRoot !== null &&
      nextFlushedExpirationTime !== NoWork &&
      (minExpirationTime === NoWork ||
        minExpirationTime >= nextFlushedExpirationTime)
    ) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, true);
      findHighestPriorityRoot();
    }
  }

  // We're done flushing work. Either we ran out of time in this callback,
  // or there's no more work left with sufficient priority.

  // If we're inside a callback, set this to false since we just completed it.
  if (deadline !== null) {
    callbackExpirationTime = NoWork;
    callbackID = null;
  }
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(
      ((nextFlushedRoot: any): FiberRoot),
      nextFlushedExpirationTime,
    );
  }

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

  finishRendering();
}
  • performAsyncWork, performSyncWork, performWork 三个方法连在一起
  • 对于 performSyncWork 直接 调用 performWork(Sync, null); 就一行代码
  • performAsyncWork 就相对复杂,是通过 react scheduler 调度回来的
    • 参数是 deadline 对象 dl
    • 本质上只有一个 if判断 和 执行 performWork 方法
    • 在if判断中, recomputeCurrentRendererTime 这个方法不涉及主要流程,跳过
      • 在找到 root 之后,执行 didExpireaTexpirationTime 标记在root节点上的一些变量
        // packages/react-reconciler/src/ReactFiberPendingPriority.js
        export function didExpireAtExpirationTime(
          root: FiberRoot,
          currentTime: ExpirationTime,
        ): void {
          const expirationTime = root.expirationTime;
          // 有任务,但任务过期
          if (expirationTime !== NoWork && currentTime >= expirationTime) {
            // The root has expired. Flush all work up to the current time.
            root.nextExpirationTimeToWorkOn = currentTime; // 挂载 当前时间 作为 nextExpirationTimeToWorkOn 属性
          }
        }
        
      • 之后,一个do while 找最终的 root
    • 之后,执行 performWork(NoWork, dl)
      • NoWork 是 0
      • export const NoWork = 0
  • performWork
    • 有两个参数,minExpirationTime: ExpirationTime, dl: Deadline | null
    • findHighestPriorityRoot 这个方法
      function findHighestPriorityRoot() {
        let highestPriorityWork = NoWork;
        let highestPriorityRoot = null;
        // 这个if表示仍存在节点更新情况
        if (lastScheduledRoot !== null) {
          let previousScheduledRoot = lastScheduledRoot;
          let root = firstScheduledRoot;
          // 存在 root 就进行循环
          while (root !== null) {
            const remainingExpirationTime = root.expirationTime;
            // 这个if判断表示,是没有任何更新的
            if (remainingExpirationTime === NoWork) {
              // This root no longer has work. Remove it from the scheduler.
      
              // TODO: This check is redudant, but Flow is confused by the branch
              // below where we set lastScheduledRoot to null, even though we break
              // from the loop right after.
              invariant(
                previousScheduledRoot !== null && lastScheduledRoot !== null,
                'Should have a previous and last root. This error is likely ' +
                  'caused by a bug in React. Please file an issue.',
              );
              // 这种情况,只有一个 root 节点
              if (root === root.nextScheduledRoot) {
                // This is the only root in the list.
                root.nextScheduledRoot = null;
                firstScheduledRoot = lastScheduledRoot = null;
                break;
              } else if (root === firstScheduledRoot) {
                // 这时候 root 就没用了,可以删除了,获取 next
                // This is the first root in the list.
                const next = root.nextScheduledRoot;
                firstScheduledRoot = next;
                lastScheduledRoot.nextScheduledRoot = next;
                root.nextScheduledRoot = null;
              } else if (root === lastScheduledRoot) {
                // 这个时候,root是最后一个
                // This is the last root in the list.
                lastScheduledRoot = previousScheduledRoot;
                lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
                root.nextScheduledRoot = null;
                break;
              } else {
                // 移除 中间的 root.nextScheduledRoot 节点
                previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
                root.nextScheduledRoot = null;
              }
              root = previousScheduledRoot.nextScheduledRoot;
            } else {
              // 判断优先级,更新更高优先级
              if (
                highestPriorityWork === NoWork ||
                remainingExpirationTime < highestPriorityWork
              ) {
                // Update the priority, if it's higher
                highestPriorityWork = remainingExpirationTime;
                highestPriorityRoot = root;
              }
              if (root === lastScheduledRoot) {
                break;
              }
              if (highestPriorityWork === Sync) {
                // Sync is highest priority by definition so
                // we can stop searching.
                break;
              }
              previousScheduledRoot = root;
              root = root.nextScheduledRoot;
            }
          }
        }
        // 处理更新后的 highestPriorityRoot 和 highestPriorityWork
        nextFlushedRoot = highestPriorityRoot;
        nextFlushedExpirationTime = highestPriorityWork;
      }
      
    • 看下 deadine 不为 null 时的情况
      • 这是异步的情况
      • 主要看 while 里的 (!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)
        • !deadlineDidExpire 表示时间片还有剩余时间
        • nextFlushedExpirationTime 是现在要输出任务的过期时间
          • currentRendererTime >= nextFlushedExpirationTime
          • 说明 当前 render 时间 比 过期时间大,已经超时了,需要强制输出
        • 之后执行 performWorkOnRoot
          • 关于这个函数,主要关注第三个参数
          • 默认是 true
          • 对于异步情况,值为: currentRendererTime >= nextFlushedExpirationTime
            • 任务过期,则为 true, 否则为 false
        • 进入 这个函数
          function performWorkOnRoot(
            root: FiberRoot,
            expirationTime: ExpirationTime,
            isExpired: boolean, // 是否过期,这个方法里一定有针对过期任务的强制更新
          ) {
            invariant(
              !isRendering,
              'performWorkOnRoot was called recursively. This error is likely caused ' +
                'by a bug in React. Please file an issue.',
            );
          
            isRendering = true; // 注意这里和函数结束
          
            // Check if this is async work or sync/expired work.
            // deadline === null  是 Sync的情况,isExpired 是过期的情况
            if (deadline === null || isExpired) {
              // Flush work without yielding.
              // TODO: Non-yieldy work does not necessarily imply expired work. A renderer
              // may want to perform some work without yielding, but also without
              // requiring the root to complete (by triggering placeholders).
          
              let finishedWork = root.finishedWork;
              if (finishedWork !== null) {
                // This root is already complete. We can commit it.
                completeRoot(root, finishedWork, expirationTime); // 有 finishedWork 直接 completeRoot
              } else {
                root.finishedWork = null;
                // If this root previously suspended, clear its existing timeout, since
                // we're about to try rendering again.
                const timeoutHandle = root.timeoutHandle;
                // 处理 timeoutHandle 的情况,跳过
                if (timeoutHandle !== noTimeout) {
                  root.timeoutHandle = noTimeout;
                  // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
                  cancelTimeout(timeoutHandle);
                }
                // 在这个条件下,不可中断 因为在上层if框架下,要么是 Sync的任务,要么是 过期的任务需要立即执行
                const isYieldy = false;
                renderRoot(root, isYieldy, isExpired);
                finishedWork = root.finishedWork;
                if (finishedWork !== null) {
                  // We've completed the root. Commit it.
                  completeRoot(root, finishedWork, expirationTime);
                }
              }
            } else {
              // 这里匹配 Async 异步任务,和上面Sync的任务流程基本差不多
              // Flush async work.
              let finishedWork = root.finishedWork;
              // 一开始进来判断这个 finishedWork,有可能在上一个时间片中,renderRoot执行完了,但是,没有时间去执行 completeRoot 了
              // 需要再下次异步调度的时候进来,如果有 finishedWork 则先 completeRoot
              if (finishedWork !== null) {
                // This root is already complete. We can commit it.
                completeRoot(root, finishedWork, expirationTime);
              } else {
                root.finishedWork = null;
                // If this root previously suspended, clear its existing timeout, since
                // we're about to try rendering again.
                const timeoutHandle = root.timeoutHandle;
                if (timeoutHandle !== noTimeout) {
                  root.timeoutHandle = noTimeout;
                  // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
                  cancelTimeout(timeoutHandle);
                }
                // 这个任务是可以中断的
                const isYieldy = true;
                renderRoot(root, isYieldy, isExpired);
                finishedWork = root.finishedWork;
                // finishedWork 有可能为 null, 中断了就没有完成任务
                if (finishedWork !== null) {
                  // We've completed the root. Check the deadline one more time
                  // before committing.
                  // 先判断是否需要跳出,这时候时间片可能已经用完,如果没有用完,执行 completeRoot
                  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.
                    // 需要跳出,则赋值 finishedWork,注意这里不执行 completeRoot,因为没有时间了,需要等待下个时间片进来才能执行
                    root.finishedWork = finishedWork;
                  }
                }
              }
            }
          
            isRendering = false;
          }
          
        • 执行 findHighestPriorityRoot
        • currentSchedulerTime = currentRendererTime
    • 看下 deadine 为 null 时的情况
      • 这是同步的情况
      • while(nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || minExpirationTime >= nextFlushedExpirationTime))
        • 对于 perfromSyncWork 来说,minExpirationTime 是 1,1 >= nextFlushedExpirationTime 说明 只有 Sync(1)的情况,或者 NoWork,所以 nextFlushedExpirationTime 只有是1的情况
        • 相当于在 perfromSyncWork 的时候,只会执行 root.expirationTime 是 Sync 的任务,也就是说是同步更新的更新,才会在这里继续执行,这样和 SyncWork 这函数名匹配
        • 在这种情况下,调用 performWorkOnRootfindHighestPriorityRoot 执行掉 Sync的任务
  • 注意,在 performWork 上两个循环的判断条件
  • 以及传入 performWorkOnRoot的第三个参数的意义
文章来源:https://blog.csdn.net/Tyro_java/article/details/135625789
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。