目录
- 1,异步更新状态
- 第2个参数
- 2,多个 setState
- 1,问题
- 2,配合第2个参数解决
- 3,第1个参数是函数
- 4,同步更新状态举例
最佳实践
- 把所有的 setState 当做异步的。
- 永远不要信任 setState 调用之后的状态(可能未更新)。
- 如果要使用改变后的状态,需要使用回调函数(setState 的第2个参数)。
- 如果新的状态,需要使用之前的状态参与计算,则使用函数的方式改变状态(setState 的第1个参数改为函数)
1,异步更新状态
this.setState() 改变状态后,会触发 render 函数执行,但不是立即执行。
而状态的改变是异步还是同步,取决于执行 setState()的方法是否通过事件调用。如果是事件调用,则是异步的。
状态的改变,才会触发 render 执行。
下面的代码中,状态更新就是异步的。所以第1次点击时输出:0,render
Pythonclass MyClassComp extends Component { state = { num: 0, }; handleClick = () => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); }; render() { console.log("render"); return ( <> {this.state.num} this.handleClick}>加1 ); } }
第2个参数
setState() 的第2个参数是函数,可获取更改后的状态。该函数在 render 之后执行(状态改变,才能获取新的状态)。
更改 handleClick 后,第1次点击输出:0,render,第2个参数 1
PythonhandleClick = () => { this.setState({ num: this.state.num + 1, }, () => { console.log('第2个参数',this.state.num); }); console.log(this.state.num); };
2,多个 setState
1,问题
更改 handleClick 如下,第1次点击后输出:0,render。页面显示 1
PythonhandleClick = () => { this.setState({ num: this.state.num + 1, }); this.setState({ num: this.state.num + 1, }); this.setState({ num: this.state.num + 1, }); console.log(this.state.num); };
原因:因为状态更改是异步的,所以上面的代码相当于:
PythonhandleClick = () => { this.setState({ num: 0 + 1, }); this.setState({ num: 0 + 1, }); this.setState({ num: 0 + 1, }); console.log(this.state.num); };
2,配合第2个参数解决
更改 handleClick 如下,第1次点击时输出:0,3次render,页面显示 3
上面说了,第2个函数参数会在状态更新后执行,所以执行顺序:
状态更新–> render --> 状态更新–> render --> 状态更新–> render
PythonhandleClick = () => { this.setState({ num: this.state.num + 1, }, () => { this.setState({ num: this.state.num + 1, }, () => { this.setState({ num: this.state.num + 1, }) }) }); console.log(this.state.num); };
3,第1个参数是函数
使用场景:如果遇到某个事件中,需要同步调用多次setState,则可使用函数的方式得到最新状态。
这种情况下,会将每个 setState 的函数参数放到一个队列中,按顺序执行。队列执行完毕后,再更新真正的 state,再执行 render(只执行了1次)。
注意是对异步 setState 的处理。同步的更新并不会合并!render 函数会执行多次。
为什么会合并?因为 React 对事件处理函数处理的思路时,元素的事件处理,可能会操作比较多的东西,如果不加限制,会影响性能。比如一个点击事件的逻辑中会触发多个自定义方法,每个方法中又会更改不同的状态。所以会将他们合并为一次修改,做成异步的,最终统一更新,执行一次 render 函数。
而不在HTML元素的事件中,不会遇到复杂的处理,比如计时器中。
在队列中的函数,可以获取上一个函数更新后的 state。换句话说,作为参数的 state 是可以被信任的(最新的)。
更改 handleClick如下,第一次点击时输出:0,1,2,3,render,页面显示 3
PythonhandleClick = () => { this.setState((curState) => { console.log(1); return { num: curState.num + 1, }; }); this.setState((curState) => { console.log(2); return { num: curState.num + 1, }; }); this.setState((curState) => { console.log(3); return { num: curState.num + 1, }; }); console.log(this.state.num); };
注意,第2个参数依旧会在 render 执行后执行。
修改第1个 setState 如下,第一次点击的输出:0,1,2,3,render,x,
Pythonthis.setState((curState) => { console.log(1); return { num: curState.num + 1, }; }, () => { console.log('x') });
4,同步更新状态举例
虽然是在点击事件中,但因为嵌套了计时器,所以可看做同步 setState。
举例1:1s 后输出:render,1
PythonhandleClick = () => { setTimeout(() => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); }, 1000); };
举例2:1s 后输出:3次render,3
PythonhandleClick = () => { setTimeout(() => { this.setState({ num: this.state.num + 1, }); this.setState({ num: this.state.num + 1, }); this.setState({ num: this.state.num + 1, }); console.log(this.state.num); }, 1000); };
举例3,异步函数不会阻塞 render 执行,但异步函数之后的 setState 算做同步。
Pythonconst delay = (duration = 1000) => { return new Promise((resolve) => { setTimeout(() => { resolve(); }, duration); }); }; handleClick = async () => { this.setState({ num: this.state.num + 1, }); // 会同步执行 render await delay(); // num 已经是修改后的 this.setState({ num: this.state.num + 1, }); console.log(this.state.num); };
第一次点击是的输出:render,(等待1s)render,2
这里,第2个 render 比 2 先输出,就是因为异步函数之后,算做同步 setState 了。
以上。
还没有评论,来说两句吧...