關於React中setState同步或異步問題的理解

1. setState同步?異步?

在 React 的類式組件中,我們可以使用setState方法更新state狀態。但有些時候使用setState之後,得不到最新的數據。

其實 React 中setState本身執行的過程和代碼是同步的,隻是因為 React 框架本身的性能優化機制而導致的。React 中合成事件和生命周期函數的調用順序在更新之前,導致在合成事件和生命周期函數中無法立刻得到更新後的值,形成瞭異步的形式。

假如在一個合成事件中,循環調用瞭setState方法n次,如果 React 沒有優化,當前組件就要被渲染n次,這對性能來說是很大的浪費。所以,React 為瞭性能原因,對調用多次setState方法合並為一個來執行。當執行setState的時候,state中的數據並不會馬上更新。

前面已經說到,在 React 的合成事件和生命周期函數中直接調用setState,會表現出異步的形式。

除此之外,如果越過 React 的性能優化機制,在原生事件、setTimeout中使用setState,就會表現出同步的形式。

2. 表現為異步

1. React 合成事件

在 React 中直接使用的事件,如onChange、onClick等,都是由 React 封裝後的事件,是合成事件,由 React 管理。那麼由於性能優化的機制,在合成事件中直接調用setState,將表現出異步的形式。

如下代碼,在合成事件onClick中,直接將state中的count加1,並在此之後打印count的值,結果第一次點擊按鈕時,會打印出0,而不是最新的1。

state = { count: 0 };
add = () => {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count); // 0
};
render() {
    return (
        <>
            <div>當前計數:{this.state.count}</div>
            <button onClick={this.add}>add</button>
        </>
    );
}

2. 生命周期函數

生命周期函數也是由 React 所管理,在生命周期函數中直接調用setState,也會表現出異步的形式。

如下代碼,在生命周期componentDidMount函數中,將state中的count加1,並在此之後打印count的值,結果打印出0,而不是最新的1。

state = { count: 0 };
componentDidMount() {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count); // 0
}
render() {
    return (
        <>
            <div>當前計數:{this.state.count}</div>
            <button>add</button>
        </>
    );
}

3. 表現為同步

1. 原生事件

setState本身執行的過程是同步的,使用原生事件,繞過 React 的管理,將表現出同步的形式。

如下代碼,通過id獲取到 DOM 元素,用原生方法綁定點擊事件。在點擊事件中,將state中的count加1,並在此之後打印count的值,結果會打印最新的count值1。

state = { count: 0 };
componentDidMount() {
    const btn = document.getElementById('btn');
    btn.onclick = () => {
        this.setState({ count: this.state.count + 1 });
        console.log(this.state.count); // 1
    };
}
render() {
    return (
        <>
            <div>當前計數:{this.state.count}</div>
            <button id="btn">add</button>
        </>
    );
}

2. setTimeout

如下代碼,在生命周期componentDidMount函數中寫瞭一個定時器setTimeout,在setTimeout內部將state中的count加1,並在此之後打印count的值,結果會打印最新的count值1。

setState雖然也是寫在生命周期componentDidMount函數中的,但並不是直接寫在componentDidMount裡,而是套瞭一層setTimeout。這樣,setState就表現出同步的形式。

state = { count: 0 };
componentDidMount() {
    setTimeout(() => {
        this.setState({ count: this.state.count + 1 });
        console.log(this.state.count); // 1
    }, 0);
}
render() {
    return (
        <>
            <div>當前計數:{this.state.count}</div>
            <button>add</button>
        </>
    );
}

4. setState的第二個參數

無論setState的對象式寫法,還是函數式寫法,都有第二個參數,為可選的回調函數,這個回調函數在狀態更新完畢、界面也更新後(render調用後)才被調用。

如下代碼所示,setState雖然直接在componentDidMount中調用,但在setState的回調函數中打印count的值,得到瞭最新的值1,因為回調函數在狀態更新完畢後才被調用,當然能得到最新的count瞭。

state = { count: 0 };
componentDidMount() {
    this.setState({ count: this.state.count + 1 }, () => {
        console.log(this.state.count); // 1
    });
}
render() {
    return (
        <>
            <div>當前計數:{this.state.count}</div>
            <button>add</button>
        </>
    );
}

到此這篇關於關於React中setState同步或異步問題的理解的文章就介紹到這瞭,更多相關React中setState同步或異步內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: