preface

In the React 16+ architecture, the React team chose not to use the requestIdleCallback API to Scheduler tasks due to compatibility and FPS limitations (up to 20 calls per second, or 20fps). Instead, MessageChannel is chosen to polyfill.

React scheduling process

The interaction flow between React and Scheduler is as follows:

  1. The React component updates its status and stores a task to the Scheduler that updates the React algorithm.
  2. Scheduler schedules the task and executes the React update algorithm.
  3. React asks Scheduler if it needs to pause after updating a Fiber during reconciliation. If you don’t need to pause, repeat step 3 and continue to update the next Fiber.
  4. If the Scheduler says it needs to pause, React returns a function that tells the Scheduler the task hasn’t finished yet. Scheduler will schedule the task at some point in the future.

In these steps, we focus on point 3, which is the need to determine whether the Scheduler needs to pause

The React task execution time

When is the React task executed

Let’s review the browser eventloop

Use the code to

/** * event loop */
while(true) {

  // Execute the macro task
  const queue = getNextQueue()
  const task = queue.pop()
  excute(task)

  // If there are microtasks, execute them
  while(microtaskQueue.hasTasks()){
    doMicrotask()
  }

  if(isRepaintTime()) {

    // Handle RAF (requestAnimationFrame)
    animationTasks = animationQueue.copyTasks();
    for(task in animationTasks) {
      doAnimationTask(task);
    }

    // Render the next framerepaint(); }}Copy the code

The Scheduler needs to satisfy the following function points

  1. Pause JS execution, return the main thread to the browser, and give the browser a chance to update the page
  2. Continue scheduling tasks at some future point to perform tasks that were not completed last time

Although each Tick round starts with a macro task, in practice the first execution of the synchronized code is a macro task, so the subsequent order can be seen as: execute the microtask queue => render (if rendering time is available) => next task

That is, we need a macro task, because the macro task will not block the loop on the next frame after rendering

Note: Ideally each frame is an Eventloop, but if the microtask goes beyond 16ms(current frame) or more than one frame, then the loop will go beyond one frame, which means it is possible to complete the microtask at frame n before rendering, which is called frame drop.

For example, drop frames

setTimeout(() = >{
    console.log('1st macro task')
    requestAnimationFrame(() = >{ console.log('the RAF execution')});const dom = document.getElementById('box')
    let n = 0
    while(n < 200){
        dom.style.left = n + 'px'
        n = n + 1
    }
    setTimeout(() = >{
        console.log('2nd macro mission')},0)
    p.then(() = >{
        let r = timeConsumingTask(40)
        console.log('First micromission', r)
    })
},2000) Print order: No1Second macro task No1A small task102334155RAF performed the first2Execution sequence of sub-macro tasks:1.Trigger after 2000ms1The second macro task, moving the DOM (not yet rendered), will be the first2Sub-macro task and no1Submicrotasks are queued2.Execute the list of microtasks, where a time-consuming task is simulated and takes about 10 seconds3.After 10 seconds, the microtask completes and the render is executed, so we see that it took 10 seconds for the DOM to move5.RAF's callback is executed at this point because it must have been executed before rendering6.Render to redraw7.New round, execute no2A macro taskCopy the code

How do I suspend the React task

ShouldYield is used to determine if the React task is completed and should be suspended in a limited amount of time. In the source code each time slice is 5ms, this value is adjusted according to the FPS of the device.

Determine if you should pause

function workLoopConcurrent() { 
    while(workInProgress ! = =null&&! shouldYield()) { performUnitOfWork(workInProgress); }}Copy the code

Calculate time slice according to FPS

function forceFrameRate(fps) {
  if (fps < 0 || fps > 125) {
    console['error'] ('forceFrameRate takes a positive int between 0 and 125, ' +
        'forcing frame rates higher than 125 fps is not supported',);return;
  }
  if (fps > 0) {
    yieldInterval = Math.floor(1000 / fps);
  } else {
    yieldInterval = 5;// The time slice defaults to 5ms}}Copy the code

shouldYield

You have a paragraph in the function, so you know that if the current time is greater than the time the task started + the yieldInterval, you are interrupting the task.

function shouldYield

//deadline = currentTime + yieldInterval.deadlineIs in theperformWorkUntilDeadlineThat's computed by the delta functionif (currentTime >= deadline) {
  / /...
	return true
}
Copy the code

MessageChannel

The postMessage function is to push a task into a macro task queue

Relative source code is lengthy

window.addEventListener('message', idleTick, false); // Accept the react task queueDidTimout flag expired - continue or call the animation, save the task until it expires - callback is not null, Invoke an expired React task. - This method ensures maximum animation execution, react update task will only execute when the time is upconst idleTick = function(event) {... }Copy the code

PostMessage is then called in requestHostCallback and animationTick

Why not use setTimeout

As mentioned above, we need a macro task, so why not use setTimeout? The reason is that under the recursive call of setTimeout, the minimum delay of loading into the queue will become 4ms, a total of 16ms for a frame. As mentioned above, the default time slice is 5ms, wasting 3~4ms is intolerable.

Why not use requestAnimationFrame

In terms of flow, RAF is executed before rendering, but the browser does not specify when the page should be rendered, so RAF is unstable.

  1. It is possible that the React Task will be put on hold for too long after loop calls RAF once several times
  2. Putting a React Task in RAF still blocks rendering

reference

www.jianshu.com/p/4a3a09925…

Xiaochen1024.com/article_ite…