performWork

performWork通过两种方式调用

performAsyncWork 异步方式

异步情况给performWork设置的minExpirationTimeNoWork,并且会判断dl.didTimeout,这个值是指任务的expirationTime是否已经超时,如果超时了,则直接设置newExpirationTimeToWorkOn为当前时间,表示这个任务直接执行就行了,不需要判断是否超过了帧时间

performSyncWork 同步方式

同步方式久比较简单了,设置minExpirationTimeSync也就是1

具体 performWork

首先要通过findHighestPriorityRoot找到下一个需要操作的root,会设置两个全局变量

这里判断是否有deadline来分成两种渲染方式,但最大的差距其实是while循环的判断条件,有deadline的多了一个条件(!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime)

我们先来看相同的部分

nextFlushedRoot !== null &&
  nextFlushedExpirationTime !== NoWork &&
  (minExpirationTime === NoWork ||
    minExpirationTime >= nextFlushedExpirationTime)

下一个输出节点不是null,并且过期时间不是NoWork,同时超时时间是NoWork,或者超时时间大于下个节点的超时时间,一般来说minExpirationTime应该就等于nextFlushedExpirationTime因为他们来自同一个 root,nextFlushedExpirationTime是在findHighestPriorityRoot阶段读取出来的root.expirationTime

在非异步的情况下,接下去就是循环执行performWorkOnRoot然后再找下一个优先级的root执行

而在异步的情况下多了一个判断(!deadlineDidExpire || currentRendererTime >= nextFlushedExpirationTime),什么意思呢?deadlineDidExpire是用来判断时间片是否到期的,也就是deadline中设置的,而第二个条件是当前渲染时间是否大于nextFlushedExpirationTime,也就是判断任务是否已经超时了了,如果超时了,根据下面调用performWorkOnRoot的参数中有一个currentRendererTime >= nextFlushedExpirationTime,也就是这种情况下为true,代表的意思是同步执行任务不再判断是否时间片到期。

收尾

最后如果跳出循环,有两种可能,一是任务已经完成,一是时间片用完了并且任务没过期。对于第二种情况会重新发起一次异步调度,等requestIdleCallback有空了再回来继续执行。

function performAsyncWork(dl) {
  if (dl.didTimeout) {
    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, dl)
}

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

performWorkOnRoot

这里也分为同步和异步两种情况,但是这两种情况的区别其实非常小。

首先是一个参数的区别,isYieldy在同步的情况下是false,而在异步情况下是true。这个参数顾名思义就是是否可以中断,那么这个区别也就很好理解了。

第二个区别就是在renderRoot之后判断一下shouldYeild,如果时间片已经用完,则不直接completeRoot,而是等到一下次requestIdleCallback之后再执行。

renderRootcompleteRoot分别对应两个阶段:

  • 渲染阶段
  • 提交阶段

渲染阶段可以被打断,而提交阶段不能

function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isExpired: boolean,
) {
  isRendering = true

  if (deadline === null || isExpired) {
    let finishedWork = root.finishedWork
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime)
    } else {
      root.finishedWork = null
      const timeoutHandle = root.timeoutHandle
      if (enableSuspense && timeoutHandle !== noTimeout) {
        root.timeoutHandle = noTimeout
        cancelTimeout(timeoutHandle)
      }
      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 {
    // 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 (enableSuspense && 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
      if (finishedWork !== null) {
        if (!shouldYield()) {
          // Still time left. Commit the root.
          completeRoot(root, finishedWork, expirationTime)
        } else {
          root.finishedWork = finishedWork
        }
      }
    }
  }

  isRendering = false
}

findHighestPriorityRoot

一般情况下我们的 React 应用只会有一个root,所以这里的大部分逻辑其实都不是常见情况

循环firstScheduledRoot => lastScheduledRootremainingExpirationTimeroot.expirationTime,也就是最早的过期时间。

如果他是NoWork说明他已经没有任务了,从链表中删除。

从剩下的中找到expirationTime最小的也就是优先级最高的root然后把他赋值给nextFlushedRoot并把他的expirationTime赋值给nextFlushedExpirationTime这两个公共变量。

一般来说会直接执行下面这个逻辑

if (root === root.nextScheduledRoot) {
  // This is the only root in the list.
  root.nextScheduledRoot = null;
  firstScheduledRoot = lastScheduledRoot = null;
  break;
}
function findHighestPriorityRoot() {
  let highestPriorityWork = NoWork
  let highestPriorityRoot = null
  if (lastScheduledRoot !== null) {
    let previousScheduledRoot = lastScheduledRoot
    let root = firstScheduledRoot
    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.
        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.nextScheduledRoot = null
          firstScheduledRoot = lastScheduledRoot = null
          break
        } else if (root === firstScheduledRoot) {
          // This is the first root in the list.
          const next = root.nextScheduledRoot
          firstScheduledRoot = next
          lastScheduledRoot.nextScheduledRoot = next
          root.nextScheduledRoot = null
        } else if (root === lastScheduledRoot) {
          // This is the last root in the list.
          lastScheduledRoot = previousScheduledRoot
          lastScheduledRoot.nextScheduledRoot = firstScheduledRoot
          root.nextScheduledRoot = null
          break
        } else {
          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
      }
    }
  }

  nextFlushedRoot = highestPriorityRoot
  nextFlushedExpirationTime = highestPriorityWork
}

下一篇:renderRoot

results matching ""

    No results matching ""

    Jokcy的二维码

    扫码添加Jokcy,更多更新更优质的前端学习内容不断更新中,期待与你一起成长!

    Jokcy的二维码