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

performWork #60

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

performWork #60

Cosen95 opened this issue Oct 27, 2020 · 0 comments

Comments

@Cosen95
Copy link
Owner

Cosen95 commented Oct 27, 2020

在开始之前,我们先回顾一下ReactrequestWork之前的工作流程。React会先根据window.performance.now()方法获取一个从地址栏输入地址回车后到执行这个方法中间间隔的毫秒数,然后通过一个常量减去这个毫秒数/10 计算出一个expirationTime,然后fiber节点会根据实际情况判断是否记录此expirationTime作为自己最终的expirationTime

然后进入updateContainer流程,updateContainer流程中会创建一个update对象,这个对象会记录这次updateexpirationTimeeffecttagnext等信息,并最终组成一个叫做updateQueue的链表存储到fiber节点中。

接下来进入scheduleWork流程,react递归的更新FiberRoot上的与expirationTime相关的属性,找到下一个工作的expirationTime

接下来就进入requestWork方法。

// requestWork is called by the scheduler whenever a root receives an update.
// It's up to the renderer to call renderRoot at some point in the future.
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // 把当前 root设置为最高优先级
  addRootToSchedule(root, expirationTime);
  if (isRendering) {
    // 在render过程当中, 此时直接return
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }

  // 批量处理相关
  // 调用 setState 时在 enqueueUpdates 前 batchedUpdates 会把 isBatchingUpdates 设置成 true
  if (isBatchingUpdates) {
    // 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;
  }

  // TODO: Get rid of Sync and use current time?
  if (expirationTime === Sync) {
    // 同步的调用 js 代码
    performSyncWork();
  } else {
    // 异步调度 独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

我们先看requestWork中调用的第一个方法addRootToSchedule,将root节点添加到调度队列中。

/**
 * 将 root 加入到调度队列
 *
 * @param {FiberRoot} root
 * @param {ExpirationTime} expirationTime
 */
function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
  // Add the root to the schedule.
  // Check if this root is already part of the schedule.

  // root.nextScheduledRoot 用来判断是否有异步任务正在调度, 为 null 时会增加 nextScheduledRoot
  // 这个 root 还没有进入过调度
  if (root.nextScheduledRoot === null) {
    // This root is not already scheduled. Add it.

    root.expirationTime = expirationTime;
    if (lastScheduledRoot === null) {
      // lastScheduledRoot firstScheduledRoot 是单向链表结构,表示多个 root 更新
      // 这里只有一个 root 只会在这里执行
      firstScheduledRoot = lastScheduledRoot = root;
      root.nextScheduledRoot = root;
    } else {
      // 有个多个root 时进行单向链表的插入操作
      lastScheduledRoot.nextScheduledRoot = root;
      lastScheduledRoot = root;
      lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
    }
  } else {
    // This root is already scheduled, but its priority may have increased.
    // 传入的 root 已经进入过调度, 把 root 的优先级设置最高
    const remainingExpirationTime = root.expirationTime;
    // 如果 root 的 expirationTime 是同步或者优先级低,增加为计算出的最高优先级
    if (expirationTime > remainingExpirationTime) {
      // Update the priority.
      // 把当前 root 的优先级设置为当前优先级最高的
      root.expirationTime = expirationTime;
    }
  }
}

还记得前面我们说过,首次渲染更新的时候expirationTime就是Sync,因此在将FiberRoot添加到调度队列中后,会进入到performSyncWork()方法。

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

performWork方法有两种调用形式,一种performSyncWork,一种performAsyncWork

/**
 * performWork 只发现了两种调用方式,performSyncWork和performAsyncWork
 * performSyncWork的minExpirationTime就是Sync,isYieldy是false
 * performAsyncWork的minExpirationTime是NoWork,isYieldy是true
 * @param {ExpirationTime} minExpirationTime
 * @param {boolean} isYieldy 任务是否可以中断
 */
function performWork(minExpirationTime: ExpirationTime, isYieldy: boolean) {
  // Keep working on roots until there's no more work, or until there's a higher
  // priority event.
  // 查找优先级最高的root
  findHighestPriorityRoot();

  if (isYieldy) {
    recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;

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

    // nextFlushedRoot 下一个将要执行的 FiberRoot
    // nextFlushedExpirationTime !== NoWork 下一个FiberRoot中还有未执行的任务

    while (
      nextFlushedRoot !== null &&
      nextFlushedExpirationTime !== NoWork &&
      minExpirationTime <= nextFlushedExpirationTime &&
      !(didYield && currentRendererTime > nextFlushedExpirationTime)
    ) {
      performWorkOnRoot(
        nextFlushedRoot,
        nextFlushedExpirationTime,
        currentRendererTime > nextFlushedExpirationTime
      );
      findHighestPriorityRoot();
      recomputeCurrentRendererTime();
      currentSchedulerTime = currentRendererTime;
    }
  } else {
    while (
      nextFlushedRoot !== null &&
      nextFlushedExpirationTime !== NoWork &&
      minExpirationTime <= nextFlushedExpirationTime
    ) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
      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 (isYieldy) {
    callbackExpirationTime = NoWork;
    callbackID = null;
  }
  // If there's work left over, schedule a new callback.
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(
      ((nextFlushedRoot: any): FiberRoot),
      nextFlushedExpirationTime
    );
  }

  // Clean-up.
  finishRendering();
}

findHighestPriorityRoot在前面就遇到过,主要工作是查找优先级最高的Root

/**
 * 查找拥有最高渲染优先级的Root,通过设置nextFlushedRoot和nextFlushedExpirationTime进行控制
 *
 */
function findHighestPriorityRoot() {
  // 最高优先级的Work
  let highestPriorityWork = NoWork;
  // 最高优先级的Root
  let highestPriorityRoot = null;
  // 判断上一个被调度的Root是否为空,如果是,就证明是首次渲染,跳出判断。
  if (lastScheduledRoot !== null) {
    // 上一个被渲染的root节点
    let previousScheduledRoot = lastScheduledRoot;
    let root = firstScheduledRoot;
    // 从调度队列中的起始节点开始递归的判断调度队列中的root节点
    while (root !== null) {
      const remainingExpirationTime = root.expirationTime;
      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.
        // 如果root的expirationTime === NoWork, 则说明root没有任务需要完成,则将root从调度队列中移除
        // 调度队列是一个环形链表
        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."
        );
        if (root === root.nextScheduledRoot) {
          // This is the only root in the list.
          // 只有一个Root的情况, 则直接清空整个调度链表
          root.nextScheduledRoot = null;
          firstScheduledRoot = lastScheduledRoot = null;
          break;
        } else if (root === firstScheduledRoot) {
          // This is the first root in the list.
          // 如果root是起始节点,起始节点后移
          const next = root.nextScheduledRoot;
          firstScheduledRoot = next;
          lastScheduledRoot.nextScheduledRoot = next;
          root.nextScheduledRoot = null;
        } else if (root === lastScheduledRoot) {
          // This is the last root in the list.
          // 如果root是结束节点,结束节点前移
          lastScheduledRoot = previousScheduledRoot;
          lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
          root.nextScheduledRoot = null;
          break;
        } else {
          // 如果root是中间节点,则删除
          previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
          root.nextScheduledRoot = null;
        }
        // 继续递归
        root = previousScheduledRoot.nextScheduledRoot;
      } else {
        // 如果root节点有更新任务
        if (remainingExpirationTime > highestPriorityWork) {
          // Update the priority, if it's higher
          // 找到优先级最高的expirationTime
          highestPriorityWork = remainingExpirationTime;
          highestPriorityRoot = root;
        }
        // 如果root是链表尾节点,跳出while循环
        if (root === lastScheduledRoot) {
          break;
        }
        // 碰到同步任务,跳出while循环
        if (highestPriorityWork === Sync) {
          // Sync is highest priority by definition so
          // we can stop searching.
          break;
        }
        previousScheduledRoot = root;
        // 继续递归
        root = root.nextScheduledRoot;
      }
    }
  }

  nextFlushedRoot = highestPriorityRoot;
  nextFlushedExpirationTime = highestPriorityWork;
}

在找到优先级最高的Root之后,由于我们这里是通过performSyncWork方法调用进入的performWork流程,因此isYieldyfalse,会递归的调用performWorkOnRoot()方法,然后通过findHighestPriorityRoot()查找下一个Root继续递归。performWorkOnRoot代码如下:

/**
 *
 *
 * @param {FiberRoot} root 进入更新流程的FiberRoot
 * @param {ExpirationTime} expirationTime 更新流程中expirationTime
 * @param {boolean} isYieldy 是否可以被中断
 */
function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isYieldy: boolean
) {
  invariant(
    !isRendering,
    "performWorkOnRoot was called recursively. This error is likely caused " +
      "by a bug in React. Please file an issue."
  );
  // 标记当前进入render流程
  isRendering = true;

  // Check if this is async work or sync/expired work.
  // 判断当前是同步更新还是异步更新
  if (!isYieldy) {
    // 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).

    // root在完成更新工作之后的fiber对象
    let finishedWork = root.finishedWork;
    // 如果FiberRoot已经完成了更新
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      // 直接完成,进入commit流程
      completeRoot(root, finishedWork, expirationTime);
    } else {
      // 还原FiberRoot
      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);
      }
      // 进入渲染流程
      renderRoot(root, isYieldy);
      finishedWork = root.finishedWork;
      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;
      // 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);
      }
      renderRoot(root, isYieldy);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Check the if we should yield one more time
        // before committing.
        if (!shouldYieldToRenderer()) {
          // 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;
}

在首次渲染时我们进入的是performSyncWork的同步调用,因此isYieldyfalse,代码会执行到renderRoot方法,进入渲染Root的流程。

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