Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scheduleWork #57

Open
Cosen95 opened this issue Oct 27, 2020 · 0 comments
Open

scheduleWork #57

Cosen95 opened this issue Oct 27, 2020 · 0 comments

Comments

@Cosen95
Copy link
Owner

Cosen95 commented Oct 27, 2020

scheduleWork

ReactDOM.rendersetStateforceUpdate最终都调用了scheduleWork,而它做了什么工作呢?

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  const root = scheduleWorkToRoot(fiber, expirationTime);
  if (root === null) {
    // 找不到 rootFiber 直接 return
    if (__DEV__) {
      switch (fiber.tag) {
        case ClassComponent:
          warnAboutUpdateOnUnmounted(fiber, true);
          break;
        case FunctionComponent:
        case ForwardRef:
        case MemoComponent:
        case SimpleMemoComponent:
          warnAboutUpdateOnUnmounted(fiber, false);
          break;
      }
    }
    return;
  }
  // isWorking: 正在执行渲染,有任务正在进行当中 在后续任务正在执行 可能被中断的情况。。!isWorking 代表没任何任务正在进行
  // nextRenderExpirationTime !== NoWork:任务可能是异步任务,并且执行到一半没有执行完,现在要把执行权交给浏览器去执行更高优先级的任务,
  // expirationTime > nextRenderExpirationTim:新的任务的 expirationTime 高于目前任务的 expirationTime
  // 新优先级任务打断低优先级任务的操作
  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);
  }
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    // Reset this back to zero so subsequent updates don't throw.
    nestedUpdateCount = 0;
    invariant(
      false,
      "Maximum update depth exceeded. This can happen when a " +
        "component repeatedly calls setState inside " +
        "componentWillUpdate or componentDidUpdate. React limits " +
        "the number of nested updates to prevent infinite loops."
    );
  }
}

总结下,scheduleWork做的工作就是:

  • 找到更新对应的FiberRoot节点。
    • 在使用ReactDOM.render的时候我们传给scheduleWork就是FiberRoot
    • 但是调用setStateforceUpdate 一般都是传递的是某个组件的对应的fiber节点,这时候就需要找到FiberRoot节点
  • 找到符合条件重置 stack 。
  • 如果符合条件就请求工作调度

scheduleWorkToRoot

scheduleWork最开始先调用了scheduleWorkToRoot,这一步非常重要:

function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
  recordScheduleUpdate();

  if (__DEV__) {
    if (fiber.tag === ClassComponent) {
      const instance = fiber.stateNode;
      warnAboutInvalidUpdates(instance);
    }
  }

  // Update the source fiber's expiration time
  // 如果 fiber 之前产生的 expirationTime 小于现在产生的优先级时 即当前的 fiber 优先级高 设置为当前节点的 expirationTime
  if (fiber.expirationTime < expirationTime) {
    fiber.expirationTime = expirationTime;
  }
  let alternate = fiber.alternate;
  if (alternate !== null && alternate.expirationTime < expirationTime) {
    // 更新 fiber.alternate 的 expirationTime
    alternate.expirationTime = expirationTime;
  }
  // Walk the parent path to the root and update the child expiration time.
  let node = fiber.return;
  let root = null;
  if (node === null && fiber.tag === HostRoot) {
    // node === null 代表 rootFiber 因为 rootFiber.return === null
    // HostRoot 同样代表 rootFiber
    root = fiber.stateNode;
  } else {
    // node 即父节点不为空,即当前不是 rootFiber 需要循环查找,当中可能会更新 childExpirationTime
    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;
      }
      // 找到 rootFiber 并赋值 stateNode
      if (node.return === null && node.tag === HostRoot) {
        root = node.stateNode;
        break;
      }
      node = node.return;
    }
  }

  if (enableSchedulerTracing) {
    if (root !== null) {
      const interactions = __interactionsRef.current;
      if (interactions.size > 0) {
        const pendingInteractionMap = root.pendingInteractionMap;
        const pendingInteractions = pendingInteractionMap.get(expirationTime);
        if (pendingInteractions != null) {
          interactions.forEach((interaction) => {
            if (!pendingInteractions.has(interaction)) {
              // Update the pending async work count for previously unscheduled interaction.
              interaction.__count++;
            }

            pendingInteractions.add(interaction);
          });
        } else {
          pendingInteractionMap.set(expirationTime, new Set(interactions));

          // Update the pending async work count for the current interactions.
          interactions.forEach((interaction) => {
            interaction.__count++;
          });
        }

        const subscriber = __subscriberRef.current;
        if (subscriber !== null) {
          const threadID = computeThreadID(
            expirationTime,
            root.interactionThreadID
          );
          subscriber.onWorkScheduled(interactions, threadID);
        }
      }
    }
  }
  return root;
}

它主要做了以下几件事:
1、根据当前Fiber节点向上寻找对应的的root节点
2、给更新节点的父节点链上的每个节点的expirationTime设置为这个updateexpirationTime,除非他本身时间要小于expirationTime
3、给更新节点的父节点链上的每个节点的childExpirationTime设置为这个updateexpirationTime,除非他本身时间要小于expirationTime
4、最终返回root节点的Fiber对象

resetStack

scheduleWork调用了scheduleWorkToRoot最终返回了rootFiber节点后呢,我们执行了一个判断,其中调用了一个很重要的方法resetStack

if (
  !isWorking &&
  nextRenderExpirationTime !== NoWork &&
  expirationTime > nextRenderExpirationTime
) {
  // This is an interruption. (Used for performance tracking.)
  // 给开发工具用的,用来展示被哪个节点打断了异步任务
  interruptedBy = fiber;
  // 新优先级任务打断低优先级任务的操作。
  resetStack();
}

这里的意思就是目前没有任何任务在执行,并且之前有执行过任务,同时当前的任务比之前执行的任务过期时间要早(也就是优先级要高)。

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

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

function resetStack() {
  if (nextUnitOfWork !== null) {
    let interruptedWork = nextUnitOfWork.return;
    while (interruptedWork !== null) {
      unwindInterruptedWork(interruptedWork);
      interruptedWork = interruptedWork.return;
    }
  }

  if (__DEV__) {
    ReactStrictModeWarnings.discardPendingWarnings();
    checkThatStackIsEmpty();
  }
  // 重置所有的公共变量
  nextRoot = null;
  nextRenderExpirationTime = NoWork;
  s;
  nextLatestAbsoluteTimeoutMs = -1;
  nextRenderDidError = false;
  nextUnitOfWork = null;
}

这里的nextUnitOfWork用于记录render阶段Fiber树遍历过程中下一个需要执行的节点。在resetStack中分别被重置,他只会指向workInProgress

markPendingPriorityLevel(后面补充)

调用 requestWork

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);
}

这个判断条件就比较简单了,!isWorking || isCommitting简单来说就是要么处于没有work的状态,要么只能在render阶段,不能处于commit阶段。

在符合条件之后就去执行requestWork了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant