react-react18前后setState

loading 2022年12月03日 111次浏览

在同步中执行setState,发现setState会异步执行。
在异步中执行setState,发现setState会同步执行。

setState第二个参数是个回调函数,state和DOM更新完后就会被触发

export default class App extends Component {
    state = {
        cnt: 1
    }
    render() {
        return (
            <div>
                {this.state.cnt}
                <button onClick={this.add1}>add1</button>
                <button onClick={this.add2}>add2</button>
            </div>
        )
    }

    //同步逻辑中setState异步执行
    add1 = () => {
        this.setState({
            cnt: this.state.cnt + 1
        }, () => {
            console.log(this.state.cnt)//2 3 4
        })

        this.setState({
            cnt: this.state.cnt + 1
        }, () => {
            console.log(this.state.cnt)//2 3 4
        })
    }

    //异步逻辑中setState同步执行
    add2 = () => {
        setTimeout(() => {
            this.setState({
                cnt: this.state.cnt + 1
            }, () => {
                console.log(this.state.cnt)//2 4 6
            })

            this.setState({
                cnt: this.state.cnt + 1
            }, () => {
                console.log(this.state.cnt)//3 5 7
            })
        }, 0)
    }
}

但是此特点在react18中已经被修复(被称为Automatic Batching,即自动批处理):不论是否处于异步逻辑中,多个state更新都会统一到一次render中渲染,以提升性能

如果不想采用这种批处理方案,可以通过flushSync来强制同步执行,直接更新一次DOM。

// 不兼容
handleClick = () => {
  setTimeout(() => {
    this.setState(({ count }) => ({ count: count + 1 }));

    // 在 React17 及之前,打印出来是 { count: 1, flag: false }
    // 在 React18,打印出来是 { count: 0, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};

// 更改后兼容
handleClick = () => {
  setTimeout(() => {
    ReactDOM.flushSync(() => {
      this.setState(({ count }) => ({ count: count + 1 }));
    });

    // 在 React18,打印出来是 { count: 1, flag: false }
    console.log(this.state);

    this.setState(({ flag }) => ({ flag: !flag }));
  });
};