前言
在React 16.8.0版本之前还并未出现HOOK,当时大部分的开发都是使用类组件编写的,那么如果一个组件有自己可维护的状态(State)必须要使用setState函数来改变状态中的数据,有时我们经常会遇到一些非常奇怪的现象,比如使用setState改变状态后立即使用状态中的数据竟然不是实时的等等。本章就深入认识一下setState,解决一些你可能会遇到的坑。
1. setState对状态的改变到底是同步的还是异步的?
要搞懂setState对状态的改变到底是同步,我做了个实验,一个是由HTML事件触发使用的setState改变状态,一个是由setInterval定时器使用setState改变状态。
import React from 'react'
export default class App extends React.Component {
state = {
n: 10,
}
handler = () => {
this.setState({
n: this.state.n + 1,
});
console.log(this.state.n);
}
render() {
console.log("组件重新渲染啦~");
return (
<div>
{this.state.n}
<button onClick={this.handler}>+</button>
</div>
)
}
}
点击button之后使用setState改变状态再打印会发现,n的值还是刚开始的10,页面上已经是11了,而且打印也在render函数之前,此时可以证明在html事件中setState对状态的改变是异步的。
接下来我在setInterval中调用setState看看此时对状态的改变是同步的还是异步的。
export default class App extends React.Component {
state = {
n: 10,
}
componentDidMount(){
this.timer = setInterval(()=>{
this.setState({
n: this.state.n - 1,
});
console.log(this.state.n);
}, 1000);
}
render() {
console.log("组件重新渲染啦~");
return (
<div>
{this.state.n}
</div>
)
}
}
从控制台打印结果可以看出来,数据不仅是同步的,而且还在render的函数调用也在打印之前,可以理解为此时setState为同步的。
那么为什么会出现这样的情况呢?别着急,接下来看一个例子你就明白react官方的良苦用心了
export default class App extends React.Component {
state = {
n: 10,
}
handler = ()=> {
this.setState({
n: this.state.n + 1,
});
this.setState({
n: this.state.n + 1,
});
this.setState({
n: this.state.n + 1,
});
console.log(this.state.n);
}
render() {
console.log("组件重新渲染啦~");
return (
<div>
{this.state.n}
<button onClick={this.handler}>+</button>
</div>
)
}
}
为什么在执行handler函数中多次执行setState方法点击后只加1呢?聪明的小伙伴可能已经想到了,因为如果setState改变状态是HTML元素触发的事件时,那么它对状态的改变就是异步的,所以不难理解,为什么每次只是+1,因为每次使用setState设置状态的时候,此时是异步的还未执行改变n的值,所以三次+1,每次的n还是未改变的n。
那么为什么要这样设计呢?在我们真实的开发环境中场景肯定不会这么简单,基本上对状态的改变是来自于HTML的事件,比如用户的一些操作,如果不使用异步的话,那么对性能的消耗是非常大的,如果用户的操作会发送网络请求时数据量过大会造成页面的卡死,不仅如此,官方还对异步改变的setState进行了优化,从上面截图可以看出异步改变了状态后才执行render函数进行重新渲染,其实内部原理是将多个setState处理函数添加到了一个队列,当队列中对状态的处理全部改变后才会调用render函数,避免了渲染性能的浪费。
2. 如果遇到某个事件中,需要对状态修改多次改如何拿到最新状态呢?
也非常简单,使用setState的第二个参数,参数为一个回调函数,当改变状态后会立即执行回调函数,可以确保状态是最新的。例如:
export default class App extends React.Component {
state = {
n: 10,
}
handler = ()=> {
this.setState({
n: this.state.n + 1,
},()=>{
this.setState({
n: this.state.n + 1,
},()=>{
this.setState({
n: this.state.n + 1,
},()=>{
console.log(this.state.n);
});
});
});
}
render() {
console.log("组件重新渲染啦~");
return (
<div>
{this.state.n}
<button onClick={this.handler}>+</button>
</div>
)
}
}
当使用这种回调函数后,每次状态改变后立马会执行第二个参数中当回调函数,当点击按钮后,n的值+3了。但是这种方法稍微有些繁琐,我们可以把setState的参数直接变成一个函数使用函数的返回值来作为最新状态,例如:
export default class App extends React.Component {
state = {
n: 10,
}
handler = ()=> {
this.setState((curState)=>{
// curState表示的为当前的状态
// 该函数的返回值会混合(覆盖)掉之前的状态
// 该函数是异步执行
return {
n: curState.n + 1,
}
});
this.setState(cur=>({n: cur.n + 1}));
this.setState(cur=>({n: cur.n + 1}));
}
render() {
console.log("组件重新渲染啦~");
return (
<div>
{this.state.n}
<button onClick={this.handler}>+</button>
</div>
)
}
}
此时运行的结果,与使用第二个参数回调函数的效果是一样的。
总结
setState,它对状态的改变,可能是异步的 如果改变状态的代码处于某个HTML元素的事件中,则其是异步的,否则是同步 如果遇到某个事件中,需要同步调用多次,需要使用函数的方式得到最新状态
最佳实践:
1.把所有的setState当作是异步的
2.永远不要信任setState调用之后的状态
3.如果要使用改变之后的状态,需要使用回调函数(setState的第二个参数)
4.如果新的状态要根据之前的状态进行运算,使用函数的方式改变状态(setState的第一个函数)
React会对异步的setState进行优化,将多次setState进行合并(将多次状态改变完成后,再统一对state进行改变,然后触发render),至此,setState的所有秘密都被我们扒出来了,只要记得最佳实践就能万无一失,文章中有错误的地方或者改进的地方欢迎留言,谢谢。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!