Skip to content
目录

scheduleWork

typescript
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  // Fiber root节点 根据fiber 向上查找当前fiber的 Fiber root
  // 从Fiber root 开始执行调度
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    return;
  }
  if (
    !isWorking &&
    nextRenderExpirationTime !== NoWork &&
    expirationTime > nextRenderExpirationTime
  ) {
    // This is an interruption. (Used for performance tracking.)
    interruptedBy = fiber;
    resetStack();
  }
  markPendingPriorityLevel(root, expirationTime);
  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
  ) {
    const rootExpirationTime = root.expirationTime;
    requestWork(root, rootExpirationTime);
  }
}

这里先scheduleWorkToRoot,这一步非常重要,他主要做了一下几个任务

  • 找到当前Fiber的 root, FiberRoot
  • 给更新节点的父节点链上的每个节点的expirationTime设置为这个update的expirationTime,除非他本身时间要大于expirationTime
  • 给更新节点的父节点链上的每个节点的childExpirationTime设置为这个update的expirationTime,除非他本身时间要大于expirationTime

最终返回 root 节点的Fiber对象

看一下scheduleWorkToRoot的代码:

scheduleWorkToRoot

typescript
function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
  if (fiber.expirationTime < expirationTime) {
    // 如果当前fiber的expirationTime小于expirationTime
    // 就更新,因为expirationTime越大,优先级越高
    fiber.expirationTime = expirationTime;
  }
  let alternate = fiber.alternate;
  if (alternate !== null && alternate.expirationTime < expirationTime) {
    // 同上
    alternate.expirationTime = expirationTime;
  }
  let node = fiber.return;
  let root = null;
  if (node === null && fiber.tag === HostRoot) {
    // 如果node等于null,说明没有父节点了,并且又是一个HostRoot
    // 说明当前fiber是RootFiber,那fiber.stateNode就是FiberRoot
    root = fiber.stateNode;
  } else {
    // 否则就是一个向上查找的过程,并将低优先级的expirationTime和childExpirationTime
    // 都更新为当前的expirationTime, 因为当前的expirationTime优先级更高
    while (node !== null) {
      alternate = node.alternate;
      if (node.childExpirationTime < expirationTime) {
        node.childExpirationTime = expirationTime;
        if (
          alternate !== null &&
          alternate.childExpirationTime < expirationTime
        ) {
          alternate.childExpirationTime = expirationTime;
        }
      } else if (
        alternate !== null &&
        alternate.childExpirationTime < expirationTime
      ) {
        alternate.childExpirationTime = expirationTime;
      }
      if (node.return === null && node.tag === HostRoot) {
        root = node.stateNode;
        break;
      }
      node = node.return;
    }
  }
  return root;
}

scheduleWork的第一个判断

typescript
if (
  !isWorking &&
  nextRenderExpirationTime !== NoWork &&
  expirationTime > nextRenderExpirationTime
)

这个判断的意思:

  1. isWorking代表是否正在工作,在开始renderRoot和commitRoot的时候会设置为 true,也就是在render和commit两个阶段都会为true
  2. nextRenderExpirationTime在是新的renderRoot的时候会被设置为当前任务的expirationTime,而且一旦他被设置,只有当下次任务是NoWork的时候他才会被再次设置为NoWork,当然最开始也是NoWork

那么这个条件就很明显了:**目前没有任何任务在执行,并且之前有执行过任务,同时当前的任务比之前执行的任务优先级要高

**
那么这种情况会出现在什么时候呢?答案就是:上一个任务是异步任务(优先级很低,超时时间是 502ms),并且在上一个时间片(初始是 33ms)任务没有执行完,而且等待下一次requestIdleCallback的时候新的任务进来了,并且超时时间很短(52ms 或者 22ms 甚至是 Sync),那么优先级就变成了先执行当前任务,也就意味着上一个任务被打断了(interrupted)

被打断的任务会从当前节点开始往上推出context,因为在 React 只有一个stack,而下一个任务会从头开始的,所以在开始之前需要清空之前任务的的stack。
然后重置所有的公共变量

typescript
nextRoot = null
nextRenderExpirationTime = NoWork
nextLatestAbsoluteTimeoutMs = -1
nextRenderDidError = false
nextUnitOfWork = null

scheduleWork的第二个判断

typescript
if (
    !isWorking ||
    isCommitting ||
    nextRoot !== root
  )

这个判断条件就比较简单了,!isWorking || isCommitting简单来说就是要么处于没有 work 的状态,要么只能在 render 阶段,以及处于 commit 阶段(比较好奇什么时候会在 commit 阶段有新的任务进来,commit 都是同步的无法打断)。还有一个选项nextRoot !== root,这个的意思就是你的 APP 如果有两个不同的 root,这时候也符合条件。
在符合条件之后就requestWork了

requestWork

typescript
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    return;
  }
  
  // 这个判断跟批量更新有关系 在事件系统那一块再来分析
  if (isBatchingUpdates) {
    if (isUnbatchingUpdates) {
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, false);
    }
    return;
  }
  
  // 在根据当前任务的优先级 来决定执行
  // 异步任务走scheduleCallbackWithExpirationTime,也就是
  // react-schedule 实现requestIdeaCallback那块
  if (expirationTime === Sync) {
    performSyncWork();
  } else {
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

addRootToSchedule

react应用其实是可以多次调用ReactDOM.render的,所以内部是可以有多个FiberRoot 只不过正常情况下我们单页应用都是一个root;

addRootToSchedule 方法做的事情就是将多个FiberRoot使用链表串联起来,并且是一个环形链表

fiber-root.png

typescript
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
  // 如果没有计划执行, 也就是不在执行队列中 root.nextScheduledRoot就是null
  if (root.nextScheduledRoot === null) {
    // 修改过期时间
    root.expirationTime = expirationTime;
    if (lastScheduledRoot === null) {
      // 将当前root加入到执行队列
      firstScheduledRoot = lastScheduledRoot = root;
      // nextScheduledRoot 指向自己 形成闭环
      root.nextScheduledRoot = root;
    } else {
      // 将当前root添加到调度队列
      lastScheduledRoot.nextScheduledRoot = root;
      lastScheduledRoot = root;
      lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
    }
  } else {
    // 当前root已经执行过了
    // 可能之前执行被打断了 现在再次执行,优先级变了 
    // 所以更新一下优先级
    const remainingExpirationTime = root.expirationTime;
    if (expirationTime > remainingExpirationTime) {
      // Update the priority.
      root.expirationTime = expirationTime;
    }
  }
}

备案号: 浙ICP备2023000081号