1. Introduction
The React Fiber architecture implementation has been analyzed. React internally implements the method requestIdleCallback, which is a frame idle task, but the Schedular + Lane mode is far more complex than requestIdleCallback. Here we will first look at what requestIdleCallback does and then try to simulate React’s implementation of a frame idle with requestAnimationFrame + MessageChannel.
2.requestIdleCallback
window.requestIdleCallback()
2.1 Concept Understanding
Figure: A simple description of the frame life cycle
RequestIdleCallback Simply says that if a frame has free time, perform a task.
The purpose is to solve the page frame loss (stuck) situation when tasks need to occupy the main process for a long time, resulting in higher priority tasks (such as animation or event tasks), unable to respond in time.
Therefore, RequestIdleCallback localization deals with tasks that are not important or urgent.
RequestIdleCallback Parameters:
- window.requestIdleCallback(callback[, options]); Callback is the callback function to execute, which receives deadline as an object.
Deadline type Deadline = {timeRemaining: () => number // The remaining free time. That's the remaining time of the frame. DidTimeout: Boolean // Whether to time out. } // Receive callback task type RequestIdleCallback = (cb: (deadline: deadline) => void, options? : Options) => numberCopy the code
2.2 implement the demo
RequestIdleCallback processing task description:
Demo: linjiayu6. Making. IO/FE – RequestI…
Making: RequestIdleCallback experiment
const bindClick = id => element(id).addEventListener('click', BindClick ('btnB') bindClick('btnC') var Work = {work.onAsyncUnit: OnOneUnit: function () {for (var I = 0; i <= 500000; I++) {}}, // process tasks onAsyncUnit: Function () {/ / free time benchmark is 1 ms const FREE_TIME = 1 / / to which one tasks let _u = 0 function cb (deadline) {/ / when the task has not been processed & While (_u < work.unit && deadline.timeremaining () > FREE_TIME) {work.ononeUnit () _u ++} If (_u >= work.unit) {// If (_u >= work.unit) {// If (_u >= work.unit) { Continues to carry out the window such as leisure. RequestIdleCallback (cb)} window. RequestIdleCallback (cb)}}Copy the code
Above is the window. RequestIdleCallback implementation process.
Core: the browser’s ability to perform a low-priority task when a frame is free.
2.3 defects
MAY BE OFFTOPIC: requestIdleCallback is called only 20 times per second – Chrome on my 6×2 core Linux machine, it’s not really useful for UI work. requestAnimationFrame is called more often, but specific for the task which name suggests.
- Experimental API, general compatibility.
- Conclusion: The FPS of requestIdleCallback is only 20ms, and the normal rendering time is controlled at 16.67ms (1s / 60 = 16.67ms). The time is higher than the page fluency appeals.
- Personal opinion: RequestIdleCallback not important and not urgent location. Because React renders content, it’s not that it’s unimportant and not urgent. The React team implemented the API on their own because it was only moderately compatible with frame rendering and didn’t quite meet the requirements of rendering.
React requestIdleCallback
To implement requestIdleCallback processing, there are two points that need to be resolved:
- When: How do I tell if a frame is free?
- Where: Where to perform a task in a frame if there is free time?
3.1 requestAnimationFrame Calculates a frame expiration point
requestAnimationFrame
-
It is up to the system to decide when to execute the callback function. It brings together all the DOM operations in each frame in a single redraw or reflow, and the redraw or reflow interval closely tracks the screen refresh rate without causing frame loss or stalling.
-
The browser refresh rate is at 60Hz and the render time is controlled at 16.67ms (1s / 60 = 16.67ms).
DOMHighResTimeStamp
The requestAnimationFrame parameters are as follows:
Type RequestAnimationFrame = (cb: (rafTime: number) => void)Copy the code
Calculates the time at which a frame expires.
Var deadlineTime; Window. SelfRequestIdleCallback = function (cb) {requestAnimationFrame (rafTime = > {/ / end time = start time + a frame takes 16.667 ms DeadlineTime = rafTime + 16.667 //...... })}Copy the code
The above uses requestAnimationFrame to calculate the end point.
Let’s leave the judgment of free time to a later stage and look at when to perform a task if you have plenty of time.
3.2 MessageChannel Macro Task Perform a task
MessageChannel()
MessageChannel creates a communication pipe with two ports, each of which can send data via postMessage, and one port can receive data from the other as long as it is bound to the onMessage callback method.
Before looking at the method implementation, you might have a question:
- Why use macro task handling?
The core is to get the main process out of the way and the browser to update the page.
Use the event loop mechanism to execute the unfinished task on the next macro task frame.
- Why not microtasks?
Go away. For an event loop, all microtasks are completed before the page is updated, so the main thread is not ceded to the browser.
- If you use macro tasks, why not use setTimeout macro tasks?
- If MessageChannel is not supported, setTimeout is the next best thing.
- The reality is: the browser is executing
setTimeout()
和setInterval()
, a minimum time threshold is set, generally 4ms.
Var I = 0 var _start = +new Date() function fn() {setTimeout(() => {console.log(), ++ I, +new Date() - _start) if (i === 10) { return } fn() }, 0) } fn()Copy the code
Therefore, using MessageChannel to perform macro tasks, and simulate setTimeout(fn, 0), there is no delay.
The implementation is as follows:
Var deadlineTime // Save tasks var callback // Establish communication var channel = new MessageChannel() var port1 = channel.port1; var port2 = channel.port2; Port2.onmessage = () => {// Check whether the current frame is free, Const timeRemaining = () => deadlinetime-performance.now (); const _timeRemain = timeRemaining(); If (_timeRemain > 0 && callback) {const deadline = {timeRemaining, // calculate the remainder of the time didTimeout: _timeRemain < 0 / / if the current frame complete callback} / / perform callback (deadline)}} window. RequestIdleCallback = function (cb) { RequestAnimationFrame (rafTime => {// End time = start time + frame time 16.667ms deadlineTime = rafTime + 16.667 // Save the task callback = cb // Send a macro task port1.postMessage(null); })}Copy the code
4. The React source requestHostCallback
SchedulerHostConfig.js
Execute macro task (callback task)
-
RequestHostCallback: Triggers a macro task performWorkUntilDeadline.
-
PerformWorkUntilDeadline: macro task processing.
- Whether there is plenty of time, there is implementation.
- Check whether there is another callback task after this callback task is executed, that is, check hasMoreWork.
- PostMessage (null);
let scheduledHostCallback = null; let isMessageLoopRunning = false; const channel = new MessageChannel(); // port2 sends const port = channel.port2; // port1 receives channel.port1. onMessage = performWorkUntilDeadline; Const performWorkUntilDeadline = () => {// If (scheduledHostCallback! == null) { const currentTime = getCurrentTime(); // Yield after `yieldInterval` ms, regardless of where we are in the vsync // cycle. This means there's always time remaining at the beginning of // the // Calculate the expiration time of a frame deadline = currentTime + yieldInterval; const hasTimeRemaining = true; Const hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime,); if (! hasMoreWork) { isMessageLoopRunning = false; scheduledHostCallback = null; } else {// If there's more work, schedule the next message event at the end // of the preceding one. Push into the next macro task queue in port.postMessage(null); } } catch (error) { // If a scheduler task throws, exit the current browser task so the // error can be observed. port.postMessage(null); throw error; } } else { isMessageLoopRunning = false; } // Yielding to the browser will give it a chance to paint, so we can // reset this. needsPaint = false; }; ScheduledHostCallback = function(callback) {scheduledHostCallback = callback; if (! isMessageLoopRunning) { isMessageLoopRunning = true; // Enter the macro task queue port.postmessage (null); }}; cancelHostCallback = function() { scheduledHostCallback = null; };Copy the code