目录
前言
在React18中,setState
操作都是异步的「不论是在哪执行,比如:合成事件,周期函数,定时器」,
目的:实现状态的批处理「统一处理」
- 有效减少更新次数,降低性能消耗
- 有效管理代码执行的逻辑顺序
原理:利用了更新队列「updater」机制来处理
- 在当前相同时间段内「浏览器此时可以处理的事情中」,遇到
setState
会立即放入到更新队列 - 此时状态/视图微更新
- 当所有的代码操作结束,会“刷新队列”「通知更新队列中的任务执行」:把所有放入到
setState
合并一起执行,只触发一次视图更新「批处理操作」
React18 和 React16 关于 setState的区别
在React18 和 React16中,关于setState是同步还是异步,是有一些区别的!
- React18中:不论在什么地方执行setState,它都是异步的「都基于updater更新队列机制,实现的批处理」
- React16中:如果在合成事件「jsx元素中基于onXXX绑定的事件中」,周期函数中,setState的操作是异步的!!但是如果setState出现在其他异步操作中「例如:定时器、手动获取DOM元素做的事件绑定(
元素.addEventListener()
)等」,它将变为同步操作「立即更新状态和让视图渲染」
打印效果如下:
setState语法
this.setState([partialState],[callback])
[partialState]
:支持部分状态更新
this.setState({
x:100 //不论总共多少状态,只修改了x,其余状态不动
})
-
[callback]
:在状态更改,视图更新完毕后触发「只要执行setState
,callback
一定执行」- 发生在
componentDidUpdate
周期函数之后「componentDidUpdate
会在任何状态更改之后触发执行」,而回调函数方式,可以在指定状态更新之后处理一些逻辑 - 特殊:即便基于
shouldComponentUpdate
阻止了状态/视图更新,componentDidUpdate
周期函数肯定不会执行,但是设置的callback
回调函数依然会触发执行
- 发生在
setState对render的影响
1、同时修改三个状态值
只渲染一次,render
中只打印一次。
class Demo extends React.Component {
state = {
x: 10,
y: 5,
z: 0
};
handle = () => {
let {
x, y, z } = this.state;
this.setState({
x:x+1,
y:y+1,
z:z+1
});
};
render() {
console.log('视图渲染:RENDER');
let {
x, y, z } = this.state;
return <div>
x:{
x} - y:{
y} - z:{
z}
<br />
<button onClick={
this.handle}>按钮</button>
</div>;
}
}
export default Demo;
2、多个setState同时修改状态值
不会立即修改状态和视图,而是放到updater
更新队列。当上下文代码执行完之后,则执行更新队列,只更新一次,且统一渲染
handle = () => {
let {
x, y, z } = this.state;
this.setState({
x: x + 1 });
console.log(this.state.x);//10
this.setState({
y: y + 1 });
console.log(this.state.y);//5
this.setState({
y: y + 1 });
console.log(this.state.z);//0
};
3、遇到异步事件的时
先合并渲染一次,等异步执行时再渲染一次
handle = () => {
let {
x, y, z } = this.state;
this.setState({
x: x + 1 });
this.setState({
y: y + 1 });
console.log(this.state.x,this.state.y);
setTimeout(() => {
this.setState({
z: z + 1 });
console.log(this.state.z);
}, 1000);
};
4、多个异步事件
点击按钮之后在,由于三个定时器时间差为0(如果时间差很小很小也可以),页面只会渲染一次。
- 在当前相同时间段内「浏览器此时可以处理的事情中」,遇到
setState
会立即放入到更新队列 - 此时状态/视图微更新
- 当所有的代码操作结束,会“刷新队列”「通知更新队列中的任务执行」:把所有放入到
setState
合并一起执行,只触发一次视图更新「批处理操作」
handle = () => {
setTimeout(() => {
this.setState({
x: x + 1 });
console.log(this.state.z);
}, 1000);
setTimeout(() => {
this.setState({
y: y + 1 });
console.log(this.state.z);
}, 1000);
setTimeout(() => {
this.setState({
z: z + 1 });
console.log(this.state.z);
}, 1000);
};
5、flushSync
flushSync
是 React 18 引入的一个 API,需要从react-dom
中导入,旨在强制同步更新状态。它使得开发者能够在某些情况下阻止 React 的批量更新机制,并立即刷新组件。
在 React 中,状态更新通常是异步的,即 React 会将多个状态更新合并到一个渲染周期中,减少不必要的 DOM 更新。这种行为称为“批量更新”。但是在某些情况下,开发者希望在执行某个操作时,立即触发渲染(而不等待 React 的批量更新)。
import {
flushSync } from "react-dom";
handle = () => {
let {
x, y, z } = this.state;
this.setState({
x: x + 1 });
flushSync(()=>{
this.setState({
y: y + 1 });
console.log(this.state);
})
this.setState({
z: this.state.x+this.state.y });
console.log(this.state);
};
点击按钮之后,视图渲染两次,数据的状态也发生了变化。其实是在flushSync
操作结束之后,会立即刷新更新队列。
具体逻辑如下图:
6、拓展
1、循环里的setState
点击按钮之后,循环20次,视图只会渲染一次,最终x在视图上显示为x->11
handle = () => {
for(let i =0;i<20;i++){
this.setState({
x: this.state.x + 1 });
}
};
2、循环里使用setState、flushSync
点击按钮之后,循环20次,更新了20
次,最终显示为30
。
handle = () => {
for(let i =0;i<20;i++){
flushSync(()=>{
this.setState({
x: this.state.x + 1 });
})
}
};
或者
handle = () => {
for(let i =0;i<20;i++){
this.setState({
x: this.state.x + 1 });
flushSync();
}
};
3、循环20次,只渲染1次
handle = () => {
for(let i =0;i<20;i++){
this.setState(prevState=>{
return {
x:prevState.x +1
}
})
}
};
prevState => { return { x: prevState.x + 1 } }
这是一个函数作为 setState 的参数。在这种方式下,prevState 是一个回调函数的参数,它代表当前组件的最新状态。
prevState.x + 1
是根据当前的状态 prevState.x
计算出新的值。也就是说,每次调用 setState
时,x
的值会基于之前的值增加 1
。