scheduler

React16.5 之后把scheduler单独发一个包了,就叫scheduler

scheduleCallbackWithExpirationTime

异步进行root任务调度就是通过这个方法来做的,这里最主要的就是调用了schedulerscheduleDeferredCallback方法(在scheduler包中是scheduleWork

传入的的是回调函数performAsyncWork,以及一个包含timeout超时事件的对象

function scheduleCallbackWithExpirationTime(
  root: FiberRoot,
  expirationTime: ExpirationTime,
) {
  if (callbackExpirationTime !== NoWork) {
    // A callback is already scheduled. Check its expiration time (timeout).
    if (expirationTime > callbackExpirationTime) {
      // Existing callback has sufficient timeout. Exit.
      return
    } else {
      if (callbackID !== null) {
        // Existing callback has insufficient timeout. Cancel and schedule a
        // new one.
        cancelDeferredCallback(callbackID)
      }
    }
    // The request callback timer is already running. Don't start a new one.
  } else {
    startRequestCallbackTimer()
  }

  callbackExpirationTime = expirationTime
  const currentMs = now() - originalStartTimeMs
  const expirationTimeMs = expirationTimeToMs(expirationTime)
  const timeout = expirationTimeMs - currentMs
  callbackID = scheduleDeferredCallback(performAsyncWork, { timeout })
}

scheduler.scheduleWork

创建一个调度节点newNode,并按照timoutAt的顺序加入到CallbackNode链表,调用ensureHostCallbackIsScheduled

这里面的expirationTime是调用时传入的timeoutAt加上当前时间形成的过期时间。

function unstable_scheduleCallback(callback, deprecated_options) {
  var startTime =
    currentEventStartTime !== -1 ? currentEventStartTime : getCurrentTime()

  var expirationTime
  if (
    typeof deprecated_options === 'object' &&
    deprecated_options !== null &&
    typeof deprecated_options.timeout === 'number'
  ) {
    // FIXME: Remove this branch once we lift expiration times out of React.
    expirationTime = startTime + deprecated_options.timeout
  } else {
    // 这里是以后把`expirationTime`从React中抽离出来之后的逻辑
  }

  var newNode = {
    callback,
    priorityLevel: currentPriorityLevel,
    expirationTime,
    next: null,
    previous: null,
  }

  // Insert the new callback into the list, ordered first by expiration, then
  // by insertion. So the new callback is inserted any other callback with
  // equal expiration.
  if (firstCallbackNode === null) {
    // This is the first callback in the list.
    firstCallbackNode = newNode.next = newNode.previous = newNode
    ensureHostCallbackIsScheduled()
  } else {
    var next = null
    var node = firstCallbackNode
    do {
      if (node.expirationTime > expirationTime) {
        // The new callback expires before this one.
        next = node
        break
      }
      node = node.next
    } while (node !== firstCallbackNode)

    if (next === null) {
      // No callback with a later expiration was found, which means the new
      // callback has the latest expiration in the list.
      next = firstCallbackNode
    } else if (next === firstCallbackNode) {
      // The new callback has the earliest expiration in the entire list.
      firstCallbackNode = newNode
      ensureHostCallbackIsScheduled()
    }

    var previous = next.previous
    previous.next = next.previous = newNode
    newNode.next = next
    newNode.previous = previous
  }

  return newNode
}

ensureHostCallbackIsScheduled

如果已经在调用回调了,就 return,因为本来就会继续调用下去,isExecutingCallbackflushWork的时候会被修改为true

如果isHostCallbackScheduledfalse,也就是还没开始调度,那么设为true,如果已经开始了,就直接取消,因为顺序可能变了。

调用requestHostCallback开始调度

这里

function ensureHostCallbackIsScheduled() {
  if (isExecutingCallback) {
    // Don't schedule work yet; wait until the next time we yield.
    return
  }
  // Schedule the host callback using the earliest expiration in the list.
  var expirationTime = firstCallbackNode.expirationTime
  if (!isHostCallbackScheduled) {
    isHostCallbackScheduled = true
  } else {
    // Cancel the existing host callback.
    cancelHostCallback()
  }
  requestHostCallback(flushWork, expirationTime)
}

cancelHostCallback = function() {
  scheduledHostCallback = null
  isMessageEventScheduled = false
  timeoutTime = -1
}

requestHostCallback

开始进入调度,设置调度的内容,用scheduledHostCallbacktimeoutTime这两个全局变量记录回调函数和对应的过期时间

调用requestAnimationFrameWithTimeout,其实就是调用requestAnimationFrame在加上设置了一个100ms的定时器,防止requestAnimationFrame太久不触发。

调用回调animtionTick并设置isAnimationFrameScheduled全局变量为true

requestHostCallback = function(callback, absoluteTimeout) {
  scheduledHostCallback = callback
  timeoutTime = absoluteTimeout
  if (isFlushingHostCallback || absoluteTimeout < 0) {
    // Don't wait for the next frame. Continue working ASAP, in a new event.
    window.postMessage(messageKey, '*')
  } else if (!isAnimationFrameScheduled) {
    isAnimationFrameScheduled = true
    requestAnimationFrameWithTimeout(animationTick)
  }
}

模拟 requestIdleCallback

因为requestIdleCallback这个 API 目前还处于草案阶段,所以浏览器实现率还不高,所以在这里 React 直接使用了polyfill的方案。

这个方案简单来说是通过requestAnimationFrame在浏览器渲染一帧之前做一些处理,然后通过postMessagemacro task(类似 setTimeout)中加入一个回调,在因为接下去会进入浏览器渲染阶段,所以主线程是被 block 住的,等到渲染完了然后回来清空macro task

总体上跟requestIdleCallback差不多,等到主线程有空的时候回来调用

animationTick

只要scheduledHostCallback还在就继续调要requestAnimationFrameWithTimeout因为这一帧渲染完了可能队列还没情况,本身也是要进入再次调用的,这边就省去了requestHostCallback在次调用的必要性

接下去一段代码是用来计算相隔的requestAnimationFrame的时差的,这个时差如果连续两次都小鱼当前的activeFrameTime,说明平台的帧率是很高的,这种情况下会动态得缩小帧时间。

最后更新frameDeadline,然后如果没有触发idleTick则发送消息

var animationTick = function(rafTime) {
  if (scheduledHostCallback !== null) {
    requestAnimationFrameWithTimeout(animationTick)
  } else {
    isAnimationFrameScheduled = false
    return
  }

  var nextFrameTime = rafTime - frameDeadline + activeFrameTime
  if (nextFrameTime < activeFrameTime && previousFrameTime < activeFrameTime) {
    if (nextFrameTime < 8) {
      nextFrameTime = 8
    }
    activeFrameTime =
      nextFrameTime < previousFrameTime ? previousFrameTime : nextFrameTime
  } else {
    previousFrameTime = nextFrameTime
  }
  frameDeadline = rafTime + activeFrameTime
  if (!isMessageEventScheduled) {
    isMessageEventScheduled = true
    window.postMessage(messageKey, '*')
  }
}

idleTick

首先判断postMessage是不是自己的,不是直接返回

清空scheduledHostCallbacktimeoutTime

获取当前时间,对比frameDeadline,查看是否已经超时了,如果超时了,判断一下任务callback的过期时间有没有到,如果没有到,则重新对这个callback进行一次调度,然后返回。如果到了,则设置didTimeouttrue

接下去就是调用callback了,这里设置isFlushingHostCallback全局变量为true代表正在执行。并且调用callback也就是flushWork并传入didTimeout

var idleTick = function(event) {
  if (event.source !== window || event.data !== messageKey) {
    return
  }

  isMessageEventScheduled = false

  var prevScheduledCallback = scheduledHostCallback
  var prevTimeoutTime = timeoutTime
  scheduledHostCallback = null
  timeoutTime = -1

  var currentTime = getCurrentTime()

  var didTimeout = false
  if (frameDeadline - currentTime <= 0) {
    if (prevTimeoutTime !== -1 && prevTimeoutTime <= currentTime) {
      didTimeout = true
    } else {
      if (!isAnimationFrameScheduled) {
        isAnimationFrameScheduled = true
        requestAnimationFrameWithTimeout(animationTick)
      }
      scheduledHostCallback = prevScheduledCallback
      timeoutTime = prevTimeoutTime
      return
    }
  }

  if (prevScheduledCallback !== null) {
    isFlushingHostCallback = true
    try {
      prevScheduledCallback(didTimeout)
    } finally {
      isFlushingHostCallback = false
    }
  }
}

flushWork

先设置isExecutingCallbacktrue,代表正在调用callback

设置deadlineObject.didTimeout,在 React 业务中可以用来判断任务是否超时

如果didTimeout,会一次从firstCallbackNode向后一直执行,知道第一个没过期的任务

如果没有超时,则依此执行第一个callback,知道帧时间结束为止

最后清理变量,如果任务没有执行完,则再次调用ensureHostCallbackIsScheduled进入调度

顺便把Immedia优先级的任务都调用一遍。

function flushWork(didTimeout) {
  isExecutingCallback = true
  deadlineObject.didTimeout = didTimeout
  try {
    if (didTimeout) {
      while (firstCallbackNode !== null) {
        var currentTime = getCurrentTime()
        if (firstCallbackNode.expirationTime <= currentTime) {
          do {
            flushFirstCallback()
          } while (
            firstCallbackNode !== null &&
            firstCallbackNode.expirationTime <= currentTime
          )
          continue
        }
        break
      }
    } else {
      if (firstCallbackNode !== null) {
        do {
          flushFirstCallback()
        } while (
          firstCallbackNode !== null &&
          getFrameDeadline() - getCurrentTime() > 0
        )
      }
    }
  } finally {
    isExecutingCallback = false
    if (firstCallbackNode !== null) {
      ensureHostCallbackIsScheduled()
    } else {
      isHostCallbackScheduled = false
    }
    flushImmediateWork()
  }
}

flushFirstCallback

代码太长不放了,他做的事情很简单

  • 如果当前队列中只有一个回调,清空队列
  • 调用回调并传入deadline对象,里面有timeRemaining方法通过frameDeadline - now()来判断是否帧时间已经到了
  • 如果回调有返回内容,把这个返回加入到回调队列

全局变量参考

isHostCallbackScheduled

是否已经开始调度了,在ensureHostCallbackIsScheduled设置为true,在结束执行callback之后设置为false

scheduledHostCallback

requestHostCallback设置,值一般为flushWork,代表下一个调度要做的事情

isMessageEventScheduled

是否已经发送调用idleTick的消息,在animationTick中设置为true

timeoutTime

表示过期任务的时间,在idleTick中发现第一个任务的时间已经过期的时候设置

isAnimationFrameScheduled

是否已经开始调用requestAnimationFrame

activeFrameTime

给一帧渲染用的时间,默认是 33,也就是 1 秒 30 帧

frameDeadline

记录当前帧的到期时间,他等于currentTime + activeFraeTime,也就是requestAnimationFrame回调传入的时间,加上一帧的时间。

isFlushingHostCallback

是否正在执行callback

results matching ""

    No results matching ""

    Jokcy的二维码

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

    Jokcy的二维码