一、深入剖析setState
1、setState是同步还是异步
情景
//index.ja
import React from 'react';
import ReactDOM from 'react-dom';
import Comp from './Comp'
ReactDOM.render(<Comp n={0} />, document.getElementById('root'));
//Comp.js
import React, { Component } from 'react'
export default class Comp extends Component {
state={
n: this.props.n
}
handleClick=()=>{
this.setState({
n: this.state.n + 1
})
console.log(this.state.n);
}
render() {
return (
<div>
<h1>{this.state.n}</h1>
<button onClick={this.handleClick}>点击+</button>
</div>
)
}
}
不难理解该代码的意思是想每次点击使n+1.并且输出n的值。但是实际情况并非如此。
我们发现页面渲染的值变了,但是输出的n值仍是0。那么改进下代码。
export default class Comp extends Component {
state={
n: this.props.n
}
handleClick=()=>{
this.setState({
n: this.state.n + 1
})
console.log(this.state.n);
}
render() {
console.log(render);
return (
<div>
<h1>{this.state.n}</h1>
<button onClick={this.handleClick}>点击+</button>
</div>
)
}
}
render会先渲染一次这里不难理解。
点击改变n的状态值,输出顺序也和我们预想的一样,状态先改变,然后触发render渲染。
那么这里我们就得出了答案:setState,它对状态的改变,可能
是异步的
为什么说是可能呢?
回忆一下前面的学习笔记不难发现,我们在构造一个倒计时的组件时
import React, { Component } from 'react'
export default class Tick extends Component {
state = {
time: this.props.time
}
constructor(props){
super(props);
let timer = setInterval(()=>{
this.setState({
time: this.state.time - 1
});
if(this.state.time === 0){
clearInterval(timer);
}
},800);
}
render() {
return (
<div>
<h1>{this.state.time}</h1>
</div>
)
}
}
这里很明显就是同步的改变状态,因此在time === 0时我们才能够清空定时器。
这里我们观察两种代码的一同,可以得出一个结论:
如果改变状态的代码处于某个HTML元素的事件中,则其是异步的(改变n的click事件是绑定在button上),否则是同步(倒计时组件未进行任何绑定)
2、如何及时获取异步后的状态值
如果我们有某些特殊的需求,需要连续改变某状态的值
export default class Comp extends Component {
state={
n: this.props.n
}
handleClick=()=>{
this.setState({
n: this.state.n + 1
})
this.setState({
n: this.state.n + 1
})
this.setState({
n: this.state.n + 1
})
}
render() {
console.log('render');
return (
<div>
<h1>{this.state.n}</h1>
<button onClick={this.handleClick}>点击+</button>
</div>
)
}
}
我们会发现这样是无效,远近就是这里异步改变,我们每次执行时n的值还是0
为此react特意为我们提供了setState的回调函数以及时获取状态改变后的值。
handleClick=()=>{
this.setState({
n: this.state.n + 1
},()=>{
console.log(this.state.n);
this.setState({
n: this.state.n + 1
},()=>{
console.log(this.state.n);
})
});
}
此时又有了新的问题,如果碰到这种多次改变状态值得需求,将产生难以阅读的回调代码。于是setState有提供了函数类参数。
handleClick=()=>{
//参数cur表示当前的状态
//该函数的返回结果,会混合(覆盖)掉之前的状态
//该函数也是异步执行
this.setState(cur => {
return {
n: cur.n + 1
}
},()=>{
console.log(this.state.n);
});
}
这个函数参数的写法,会使cur参数的值变得值得信赖。(它会形成一个队列,等待前一个状态改变后将新的状态值给新的setState调用);
handleClick=()=>{
this.setState(cur => ({
n: cur.n + 1
}
));
this.setState(cur => ({
n: cur.n + 1
}
));
this.setState(cur => ({
n: cur.n + 1
}
));
}
如果遇到某个事件中,需要同步调用多次,需要使用函数的方式得到最新状态。
setState最佳实践:
- 把所有的setState当作是异步的
- 永远不要信任setState调用之后的状态
- 如果要使用改变之后的状态,需要使用回调函数(setState的第二个参数)
- 如果新的状态要根据之前的状态进行运算,使用函数的方式改变状态(setState的第一个函数)
3、react的render优化
React会对异步的setState进行优化,将多次setState进行合并(将多次状态改变完成后,再统一对state进行改变,然后触发render)
handleClick=()=>{
this.setState(cur => ({
n: cur.n + 1
}
),()=>{
console.log(this.state.n);
});
this.setState(cur => ({
n: cur.n + 1
}
));
this.setState(cur => ({
n: cur.n + 1
}
));
}
render() {
console.log('render');
return (
<div>
<h1>{this.state.n}</h1>
<button onClick={this.handleClick}>点击+</button>
</div>
)
}
这里虽然state.n状态改变了三次,但是由于是异步的setState,所以只触发最终的render,也包括了setState的回调函数此时为3。
博主开始运营自己的公众号啦,感兴趣的可以关注“飞羽逐星”微信公众号哦,拿起手机就能阅读感兴趣的博客啦!