React 中的异步函数 seState

1,433 阅读3分钟

setState 是一个异步函数,具体表现在两个方面,在一个组件中连续执行 setState 方法,会发生合并;在一个组件树中,多个组件连续执行 setState 方法,多个组件的 setState 方法会发生合并。setState 方法的执行会引起组件 render 函数的执行,判断 setState 方法是否发生合并的依据是:组件中 render 函数执行的次数是否能和 setState 方法执行的次数对应上。

1. 组件中 setState 方法的合并

直接上代码:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0,
        };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        console.log('click happened');
        this.setState({ count: this.state.count + 1 });
        this.setState({ count: this.state.count + 1 });
    }

    render() {
        console.log('App render');
        const { count } = this.state;
        return (
            <Fragment>
                <div>{ count }</div>
                <button onClick={ this.handleClick }>Click</button>
            </Fragment>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));

代码思路很简单,点击组件的按钮,执行两次组件的 setState 方法,观察组件 render 函数的执行次数,结果如下图:

组件中setState方法的合并
从结果中,可以看出,setState 方法执行了两次,render 函数只执行了一次,所以,在一个组件中连续执行 setState 方法,会发生合并。

上面 demo 中 setState 方法的参数是一个对象,setState 方法的参数还可以是一个函数,setState 方法的参数如果是一个函数,在发生合并时,与参数是对象是有差异的,直接上代码与结果:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0,
        };
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
        console.log('click happened');
        this.setState((state) => ({ count: state.count + 1 }));
        this.setState((state) => ({ count: state.count + 1 }));
    }

    render() {
        console.log('App render');
        const { count } = this.state;
        return (
            <Fragment>
                <div>{ count }</div>
                <button onClick={ this.handleClick }>Click</button>
            </Fragment>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));

组件中setState方法的合并
通过对比前后结果,可以看出,如果 setState 方法的参数是一个对象,state.count 不会有累加效果发生,如果 setState 方法的参数是一个函数,state.count 会有累加效果发生。

2. 多个组件 setState 方法的合并

直接上代码:

import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';

const setStateArr = [];

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            appCount: 0,
        };
        this.setStateHappen = this.setStateHappen.bind(this);
        setStateArr.push(this.setStateHappen);
        this.handleClick = this.handleClick.bind(this);
    }

    setStateHappen() {
        console.log('App setState happend');
        this.setState({ appCount: 1 });
    }

    handleClick() {
        console.log('App click happend');
        setStateArr.forEach((setStateFunc) => {
            setStateFunc();
        });
    }

    render() {
        console.log('App render');
        const { appCount } = this.state;
        return (
            <Fragment>
                <div>appCount: { appCount }</div>
                <Children />
                <button onClick={ this.handleClick }>App Click</button>
            </Fragment>
        );
    }
}

class Children extends Component {
    constructor(props) {
        super(props);
        this.state = {
            childrenCount: 0,
        };
        this.setStateHappen = this.setStateHappen.bind(this);
        setStateArr.push(this.setStateHappen);
    }

    setStateHappen() {
        console.log('Children setState happend');
        this.setState({ childrenCount: 1 });
    }

    render() {
        console.log('Children render');
        const { childrenCount } = this.state;
        return (
            <div>childrenCount: { childrenCount }</div>
        );
    }
}

ReactDOM.render(<App />, document.getElementById('root'));

代码的思路很简单,点击父组件的按钮时,连续执行父组件和子组件的 setState 方法,观察父组件和子组件 render 函数的执行次数。在我以前的理解中,setState 方法会引起组件 render 函数的执行,我预想的结果是,当我点击按钮时,执行父组件的 setState 方法,父组件 setState 方法的执行引起父组件 render 函数的执行,父组件 render 函数的执行引起子组件 render 函数的执行,执行子组件 setState 方法,子组件的 setState 方法引起子组件 render 函数的执行,所以,我预想的结果是,子组件的 render 函数会执行两次。实际结果如下图:

组件树中setState方法的合并
从结果看出,当点击父组件的按钮时,先执行父组件的 setState 方法、子组件的 setState 方法,接着执行父组件的 render 函数,子组件的 render 函数。子组件的 render 函数只执行了一次。故,在一个组件树中,多个组件连续执行 setState 方法,多个组件的 setState 方法会发生合并。

另,setState 设计成异步的,主要是从性能方面考虑,避免过多的 render 函数执行,虚拟 DOM 的比较。