Talk about time slices?
One, foreword
-
Recently, I have been learning the react principle and learned that react applies the concept of time slice. So through this article, I want to talk about my own understanding; And through part of the Demo, to illustrate how to use the idea of time slice;
-
Before react15 include, from the root node to cut and process, is to use recursive way, this way is not to interrupt, because be synchronized, and js thread and GUI thread is mutually exclusive, so when the project is more and more big, can cause users to be able to clearly aware of this process will be caton, Operations (such as user input) have no effect. In fact, the JS synchronization task is too long, causing the browser to drop frames; The React team spent two years refactoring to use Fiber architecture; Finally solved the problem;
-
Among them, the idea of time slice is adopted;
-
Q: What is time slice?
-
A: Basically, the synchronization task is cut into small pieces. Execute the piece-by-piece task without dropping frames in the browser; The result is that the large task can be completed and the browser has time to draw; In the user’s perception, is relatively smooth;
And today I’m trying to talk about the concept of time slices in my own sense; How to use, and provide several cases; For your reference;
2, preparation,
Prepare a working React project Demo; React/react/react/react/react/react/react/reactCopy the code
Third, synchronous rendering
First simulate a synchronous render and write a synchronous task that must be executed for 5 seconds; And in order to better show the blocking effect of JS, we write an animation, if in the process of synchronous rendering of our animation failure, then we can obviously feel the JS thread and GUI thread are mutually exclusive this statement;
import React, { PureComponent } from "react";
import "./index.css";
class Home extends PureComponent {
state = {
taskNum: 0.fiberNum: 0}; syncCalc =() = > {
let now = performance.now(), /* Start time */
index = 0;
while (performance.now() - now <= 5000) { // Must be executed for 5 seconds and is synchronous
index++;
}
this.setState({ taskNum: index });
};
render() {
return (
<div>
<h1>Time slice case</h1>
<p>
<span>Testing:</span>
<input />
</p>
<button onClick={this.syncCalc}>Synchronous rendering {this.state.tasknum}</button>
<div className="box"></div>
</div>); }}export default Home;
Copy the code
Here is the CSS section
.box {
width: 100px;
height: 100px;
background: red;
animation: normal-animate 5s linear infinite;
margin: 50px 0;
position: absolute;
}
@keyframes normal-animate {
0% {
left: 0px;
}
50% {
left: 100px;
}
100% {
left: 0px; }}Copy the code
We can see the effect:
We can see that the lag effect is very obvious, and as we expected, it will lag for 5 seconds. To better illustrate this problem, I use chrome performance analysis tool to illustrate;
I randomly truncated a frame in the process, and you can see that the entire time frame was occupied by JS, so the browser didn’t have time, or was forced to drop frames; And the frame loss lasts for about 5 seconds;
In the above CSS animation, you can see that I named it normal-animate to distinguish between CSS3 animation; You can change the animation to CSS3 animation;
.box {
width: 100px;
height: 100px;
background: red;
animation: css3-animate 5s linear infinite;
margin: 50px 0;
position: absolute;
}
@keyframes css3-animate {
0% {
transform: translateX(0px);
}
50% {
transform: translateX(100px);
}
100% {
transform: translateX(0px); }}Copy the code
If you use CSS3 animation; I’m sorry to tell you that the blocking effect is not there, but the GUI rendering thread and the JS engine thread are mutually exclusive.
This is actually because CSS3 has hardware acceleration on; The animation we see is running because CSS3 animation is controlled by GPU and supports hardware acceleration without software rendering; Here we should also advocate the use of CSS3 in the usual development as far as possible, for our project empowerment;
In the following example, we will change the animation to normal-animate. Because although we can use CSS3 animation, part of the animation problem, but the user’s operation, such as input in the input box (you can try!) , is still blocking, so we still need to solve this problem; So what’s the solution?
At least react uses the idea of the requestIdleCallback Api; The API documentation is here;
If the browser has any free time during each frame (16.6ms), the API is called and a parameter is passed (this parameter reflects how much free time is left).
With this in mind let’s take a look at using slice rendering;
4. Slice rendering
import React, { PureComponent } from "react";
import "./index.css";
class Home extends PureComponent {
state = {
taskNum: 0.fiberNum: 0}; syncCalc =() = > {
let now = performance.now(),
index = 0; // The current time;
while (performance.now() - now <= 5000) {
index++;
}
this.setState({ taskNum: index });
};
fiberCalc = () = > {
let now = performance.now(),
index = 0;
if (window.requestIdleCallback) {
requestIdleCallback(func);
}
const _this = this;
function func(handle) {
// This boundary condition is particularly important; The return value of handle.timeremaining () reflects whether the task can continue;
while (performance.now() - now <= 5000 && handle.timeRemaining()) {
index++;
}
if (performance.now() - now <= 5000) {
requestIdleCallback(func);
_this.setState({ fiberNum: index }); }}};render() {
return (
<div>
<h1>Time slice case</h1>
<p>
<span>Testing:</span>
<input />
</p>
<button onClick={this.syncCalc}>Synchronous rendering {this.state.tasknum}</button>
--
<button onClick={this.fiberCalc}>Slice rendering {this.state.fibernum}</button>
<div className="box"></div>
</div>); }}export default Home;
Copy the code
If you compare these two cases together, you can see a very clear change; Synchronous rendering blocks animation, cannot input and other operations; However, slice rendering does not block animation and can carry out input and other operations;
Again, to better illustrate the problem, I used the performance analysis tool again and looked at it;
I still truncated the process of one frame, and you can see that in almost every frame, rendering and drawing is done, which results in the relatively smooth effect we see. Why is this possible? That’s because in our loop condition, synchronization is performed only when the browser has free time, which is true for the handle.timeremaining (). When it is false, synchronization is interrupted. In between, the browser has time to draw, and then we ask the browser for a new task to process, over and over again, until the task is done!
5. Browser compatibility
We seem to have implemented slice rendering, but looking at the MDN documentation, we can see that the requestIdleCallback API is not very compatible, so with this in mind, we may not be able to use it directly; In fact, the React team didn’t use the API directly to solve the problem. Instead, they polyfilled it in a different way. I’m not going to tell you how to do that; If you want to know about it, you can look at it here;
If you think about it, can you slice time with what you already know? In fact, the essence of time slice is to interrupt the execution of the function. If we can interrupt the execution of the synchronization task at the right time, is it the right direction? Generater can interrupt function execution in JS. And when does that stop? To avoid blocking rendering, we can use macro tasks, because according to the event loop mechanism, each new event loop does not block the GUI thread drawing, so we can use the first main thread to execute the task. Only one task may be processed, but we can wake up the execution of a new task in the next event loop. Go round and round;
Why don’t we take a look at the code?
import React, { PureComponent } from "react";
import "./index.css";
class Home extends PureComponent {
state = {
taskNum: 0.fiberNum: 0}; syncCalc =() = > {
let now = performance.now(),
index = 0; // The current time;
while (performance.now() - now <= 5000) {
index++;
}
this.setState({ taskNum: index });
};
fiberCalc = async() = > {function* fun() {
let now = performance.now(),
index = 0;
while (performance.now() - now <= 5000) {
console.log(index);
yieldindex++; }}let itor = fun(); // Get an iterator;
let slice = async (data) => {
const { done, value } = itor.next();
await new Promise((resolve) = > setTimeout(resolve));
if(! done) {return slice(value);
}
return data;
};
const res = await slice();
console.log(res, "res");
};
render() {
return (
<div>
<h1>Time slice case</h1>
<p>
<span>Testing:</span>
<input />
</p>
<button onClick={this.syncCalc}>Synchronous rendering {this.state.tasknum}</button>
--
<button onClick={this.fiberCalc}>Slice rendering {this.state.fibernum}</button>
<div className="box"></div>
</div>); }}export default Home;
Copy the code
I’m using setTimeout to wake up the macro task here; So each event loop performs a task; The next task is executed in the next event loop; Look at the effect;
Let’s look at frames again;
Not bad, rendered and drawn during each frame;
Sixth, slice optimization rendering
But it doesn’t seem to be very utilized; Because each event loop only performs one task; The idle events of each frame are not well utilized; Can you take full advantage of the browser’s spare events like requestIdleCallback?
In fact, we can increase the number of times synchronization code executes per frame as much as possible; To achieve this optimization, but how do you control the key? We can set a standard expiration time, each time cycle, we are only allowed to perform 10ms synchronization tasks; Instead of just performing a synchronization task;
import React, { PureComponent } from "react";
import "./index.css";
class Home extends PureComponent {
state = {
taskNum: 0.fiberNum: 0}; syncCalc =() = > {
let now = performance.now(),
index = 0; // The current time;
while (performance.now() - now <= 5000) {
index++;
}
this.setState({ taskNum: index });
};
fiberCalc = async() = > {function* fun() {
let now = performance.now(),
index = 0;
while (performance.now() - now <= 5000) {
console.log(index);
yieldindex++; }}let itor = fun(); // Get an iterator;
let slice = async (data) => {
const { done, value } = itor.next();
await new Promise((resolve) = > setTimeout(resolve));
if(! done) {return slice(value);
}
return data;
};
const res = await slice();
console.log(res, "res");
};
betterCalc = async() = > {function* fun() {
let now = performance.now(),
index = 0;
while (performance.now() - now <= 5000) {
console.log(index);
yieldindex++; }}let itor = fun(); // Get an iterator;
let slice = (time = 10) = > {
let start = performance.now(),
index = 0,
isFinish = false;
while(! isFinish && performance.now() - start <= time) { index++;const { done } = itor.next();
isFinish = done;
}
if(! isFinish) {setTimeout(() = > {
index = slice(time);
});
}
return index;
};
slice();
};
render() {
return (
<div>
<h1>Time slice case</h1>
<p>
<span>Testing:</span>
<input />
</p>
<button onClick={this.syncCalc}>Synchronous rendering {this.state.tasknum}</button>
--
<button onClick={this.fiberCalc}>Slice rendering {this.state.fibernum}</button>
--
<button onClick={this.betterCalc}>Slice optimization rendering</button>
<div className="box"></div>
</div>); }}export default Home;
Copy the code
Take a look at the effect;
We can use the browser’s Perfermance analysis tool.
Can be analyzed in combination with the size of the printed index; Because the index value, in fact, has a larger peak, so it reflects, this is actually a more efficient way to use the browser;
You can see that the optimized code has greatly improved browser utilization; I need to remind you here; The synchronization task, for example, is a task that takes 5 seconds anyway; The larger the index, the more tasks the browser is actually handling. Because a normal task is certainly a quantitative one; Requires the browser to execute one by one; The task of consuming this quantity; The task in the case is just to better simulate the synchronization task and see the effect;
Vii. Reference documents
Time sharding technology (to solve the page lag caused by long JS tasks)
Hardware acceleration for CSS animation
React How to polyfill requestIdleCallback
React Technology revealed