在 react 中,setState 是用以改变 class 组件状态的函数,它有两种用法:
一 传入一个 updater 函数,该函数有两个参数,一个是当前的 state,还有一个是当前的 props。该函数的返回值需要是一个更改的 state 值的对象,它将于 state 进行浅合并,其用法如下:
this.setState((state, props) => {return { count: state.count + props.number};
});
二 直接传入一个对象:
this.setState({count: this.state.count + this.props.number});
setState 函数还可以接受第二个参数,该参数为一个函数,将在更改的 State 生效之后调用:
console.log(this.state.count); // 1
this.setState({count: 0}, () => {console.log(this.state.count); // 0
});
console.log(this.state.count); // ? 此处即可能是 1,也可能是 0
从上面代码可以看到,最后一行输出的 count 是不固定的,这是为什么呢?
因为在 react 中,class 内的事件处理程序会默认进行批处理,即如果你在 componentDidMount 里面调用三次 setState 函数,那么它最终会在 componentDidMount 执行完毕后,将三个 State 的更改合并为一次调用。所以这时候 setState 就是异步的。
而在其他场景下,setState 将会是同步的,例如 setTimeout 内, Promise 的 then 里面。
一个简单的例子:
class SetStateExample extends Component {constructor() {super();
this.state = {count: 0};
this.onClick = this.onClick.bind(this);
}
componentDidMount() {console.log('componentDidMount before', this.state.count);
this.setState({count: this.state.count + 1});
console.log('componentDidMount after', this.state.count);
}
onClick() {console.log('onClick before', this.state.count)
this.setState({count: this.state.count + 1}, () => {console.log('setState callback', this.state.count);
});
console.log('onClick after', this.state.count);
Promise.resolve().then(() => {console.log('promise.then before', this.state.count);
this.setState({count: this.state.count + 1});
console.log('promise.then after', this.state.count);
this.onClassEvent();});
}
onClassEvent() {console.log('onClassEvent before', this.state.count);
this.setState({count: this.state.count + 1});
console.log('onClassEvent after', this.state.count);
}
render() {
return <div className="test">
<div>count: {this.state.count}</div>
<button onClick={this.onClick}> 点击改变 count</button>
</div>;
}
}
让我们运行结果:
首先第一第二行输出是在 componentDidMount 里面,我们在函数内调用了 setState,并在前后分别输出了改变的值,结果表明,函数调用前与函数调用后该值并没有立即改变,则表明在这里 setState 是一个异步调用。那么初步判定在生命周期函数内部,setState 是异步的调用。
然后第三第四行输出是在 onClick 函数的回调里面,该函数定义在 class 中,通过用户点击触发。在 setState 调用前后我们的输出结果是一致的,这也表明其是一个异步调用。而在 setState 第二个参数中我们输出了改变后的 count, 即第五行输出,表明我们的更改生效了。
然后第六行以后的输出是我们在 onClick 函数内调用了 promise.resolve().then() 输出的,它是一个异步调用,react 是无法知道它什么时候执行,什么时候完成执行的,所以这时候 react 默认 setState 是同步的。从输出我们可以看到每次更改之后,state 的值都是立即变化生效的。
而在 promise 的回调内,我们还调用了一个定义于 class 内的事件函数,但是该事件函数内的 setState 也是同步的形式。这说明了 setState 的同步或者异步与其定义位置并没有直接的关系,而应该取决于是否由 React 直接进行调用,因为只有是 React 直接调用的情况下,它才知道该函数什么时候执行完毕,才能进行批处理的优化。否则则默认是同步的调用。(具体内部实现就不展开了,因为我也不是特别懂 HHHH,反正意思就大概是这么个意思)
所以当在一些回调内部调用 setState 时应该注意将多个 setState 合并,因为它是同步的,多次更新状态会很影响性能。
以及需要注意进行异步调用的时候,如果需要使用变化后的值,请确保在异步调用完成后,一般是在 setState 的回调内,或者在 componentDidUpdate 钩子内,但是请注意小心使用,因为很容易一不小心导致循环调用而崩溃。
如果你想在本应同步调用的回调内,对 setState 进行异步调用,即让它进行批处理,React 也提供了一个 API:
promise.then(() => {
// Forces batching
ReactDOM.unstable_batchedUpdates(() => {this.setState({a: true}); // Doesn't re-render yet
this.setState({b: true}); // Doesn't re-render yet
this.props.setParentState(); // Doesn't re-render yet});
// When we exit unstable_batchedUpdates, re-renders once
});
在 unstable_batchedUpdates 内部进行的 setState 会是异步调用,但是该 API 是不稳定的,因为后续的 React 版本更新中将会默认进行批处理即异步调用,届时该 API 将被删除。而这个后续的版本,很可能就是 React 17
记录与分享,欢迎斧正,虚心求教
参考连接:
https://stackoverflow.com/que…
https://react.docschina.org/d…
https://github.com/Advanced-F…
https://github.com/sisterAn/b…