你不知道的setState

195 阅读4分钟

可能很多同学都觉得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执行完毕之后才会执行的。

关注我,一周带你看一篇对你有收获的博客。