可能很多同学都觉得setState不就是一个同步执行的函数吗?怎么还会有异步情况。但是在一些情况下setState确实存在异步执行的情况。 首先向大家先简单介绍一下setState的使用:
setState(res1[,res2]);
setState有两个参数,第一个参数可以是一个对象,也可以是一个函数
第一个参赛是对象
this.setState({
n: 1
})
第一个参数是函数
this.setState((state) => {
return {
n: 1
}
})
其实函数式写法是将对象当做返回值返回出去,不过重要的是它的参数,这个函数的参数其实就是目前的state。
class DeepSwtState extends Component {
state = {
n: 1
}
handleClick = ()=> {
this.setState(cur => {
console.log('第一个参数函数式的写法', cur); // 输出 第一个参数函数式的写法 {n: 1}
return {
n: cur.n + 1,
a: 1
}
})
}
render() {
return (
<>
<span>{this.state.n}</span>
<button onClick={this.handleClick}>+</button>
</>
);
}
}
那么接下来说setState的第二个参数:第二个参数是一个可选的函数,不过这个函数的参数没有什么意义,重要的是对第一个参数执行结果的处理,接下来将详细说明。
接下来向大家简单介绍一下setState的异步情况。
class DeepSwtState extends Component {
state = {
n: 1
}
handleClick = ()=> {
this.setState({
n: this.state.n + 1
})
console.log('n的值: ', this.state.n); // 先输出 'n的值: ' 1
}
render() {
console.log('render执行了'); // 后输出
return (
<>
<span>{this.state.n}</span>
<button onClick={this.handleClick}>+</button>
</>
);
}
}
从上面的代码输出情况不难看出,是先输出n的值,n的值为1。但是不是已经使用setState将n的值改变了吗?怎么输出的n的值还是1。 react是当数据改变后,会先执行render函数,重新渲染页面,但是现在是后输出,就说明在点击事件发生后先执行了handleClick函数,并直接输出了 n 的值,然后改变数据渲染页面,这个时候的 setState 是异步的。
那么怎么做可以让render先渲染,然后再输出n的值,其实利用setState的第二个参数就可以解决。
接下来将提供一个方法,也就是利用setState的第二个函数进行操作。
class DeepSwtState extends Component {
state = {
n: 1
}
handleClick = ()=> {
this.setState({
n: this.state.n + 1
}, () => {
console.log('n的值回调函数中输出的: ', this.state.n); // 第三个执行输出值为 2
})
console.log('n的值: ', this.state.n);// 第一个执行 输出值为 1
}
render() {
console.log('render执行了'); // 第二个执行
return (
<>
<span>{this.state.n}</span>
<button onClick={this.handleClick}>+</button>
</>
);
}
}
但是如果页面中有多个setState进行更改state值的操作那该怎么做呢?输出情况应该是什么样的呢?
this.setState({
n: this.state.n + 1
})
console.log('n的值:', this.state.n); // 输出:1
this.setState({
n: this.state.n + 1
})
console.log('n的值:', this.state.n); // 输出:1
this.setState({
n: this.state.n + 1
})
console.log('n的值:', this.state.n); // 输出:1
如果一个 HTML 事件中有多个setState,那么由于异步执行机制,都会输出1,并且页面显示也是只加1。
输出只加1的原因:由于这里的 this.setState 是异步的,所以在经过第一个 this.setState 后,继续往下一个 this.setState 走,但是上一个的值还没有改变完成,所以第一个的 this.state.n 仍然是 1,如此类推第三个接收到的 n 也是 1。
解决这种情况的方法目前我有两个,希望有更好方案的同学可以在下面评论。
一个是第一个参数正常写为对象,第二个对象是函数,将后面的this.setState写在第二个参数里面,接着进行数据处理。
class DeepSwtState extends Component {
state = {
n: 1
}
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 (
<>
<span>{this.state.n}</span>
<button onClick={this.handleClick}>+</button>
</>
);
}
}
这样就可以看到点击按钮加3了,不过这样写起来真的是很难受,嵌套的头皮发麻。
那么就有请第二种方法登场: 第二种方法就是利用上面说到的setState的第一个参数是函数形式。
this.setState(cur => {
return {
n: cur.n + 1
}
})
this.setState(cur => ({ n: cur.n + 1}))
this.setState(cur => ({ n: cur.n + 1}))
这种方式看起来很友好,比较舒服,点击按钮也是会直接加3.
不过在这种情况下调用了几次render函数呢?在后面加上第二个参数是什么时候执行呢?会不会跟之前一样执行完这个setState就执行还是等全部setState都执行完成之后再执行?
接下来我将这些问题一一说明:
首先,改变了这么多次的state,那么render是不是也会改变多次,其实这里的render只会执行一次,大家可以去试一下我说的对不对。
其次,第二个参数什么时候执行。第二个参数是等到所有的setState执行完之后才会执行的。
class DeepSwtState extends Component {
state = {
n: 1
}
handleClick = ()=> {
this.setState(cur => {
return {
n: cur.n + 1,
a: 1
}
}, () => {
console.log(this.state); // 输出 {n: 4, a: 1, b: 2, c: 3}
console.log('第一个异步setState执行完成,n的值为:', this.state.n); // 输出 第一个异步setState执行完成,n的值为: 4
})
this.setState(cur => ({
n: cur.n + 1,
b: 2
}))
this.setState(cur => ({
n: cur.n + 1,
c:3
}))
}
render() {
console.log('render执行了');
return (
<>
<span>{this.state.n}</span>
<button onClick={this.handleClick}>+</button>
</>
);
}
}
从this.state的输出和this.state.n的输出来看,这两个输出的值都是经过全部的setState执行后的结果,所以就不难看出当有很多setState的情况下,第二个参数是所有的setState执行完毕之后才会执行的。
关注我,一周带你看一篇对你有收获的博客。