React setState -- 「異步」

setState 的執行是一個 '異步' 的過程,爲何會將這兩個字用引號代替,確定其中有必定的道理,下面對 setState 進行分析javascript

setState 源碼分析(v16.10.0)

React 源碼setState 中傳入兩個參數 partialState, callbackjava

setState 部分源碼

  • partialState 在字面意思理解應該是部分狀態,註釋說明是這樣的 Next partial state or function to produce next partial state to be merged with current state.,大概翻譯是 下一個部分狀態或函數,以產生下一個要與當前狀態合併的部分狀態。。實際上,在項目中 this.setState({}) 更新指定 state 時,其餘的 state 與當前更新的 state 作了合併。
  • callback 回調函數,意指狀態更新後的回調,在該函數中獲取最新的 state
  • setState 處理過程

首先調用了 invariant(), 其次調用了 this.updater.enqueueSetState()react

/packages/shared/invariant.js, 這個方法就是判斷 partialState 的類型是否正確,拋出錯誤,附上源碼:
git

可是在 V16.7.0版本以前 invariant 拋出的是不一樣類型的錯誤:
github

/packages/react-dom/src/server/ReactPartialRenderer.js,把即將更新的 state push 到了 queue 中,在 new Component 時,將 updater 傳進去,附上源碼:
antd

queue 應該是 React 提高性能的關鍵。由於並非每次調用 setState, React 都會立馬更新,而是每次調用 setState, React 只是將其 push 到了待更新的 queue 中,附上源碼:
dom

/packages/react-reconciler/src/ReactFiberClassComponent.js 中的 enqueueSetState;異步

/packages/react-reconciler/src/ReactUpdateQueue.js 中的 enqueueUpdate;函數

過程分析

  1. 鉤子函數及合成方法 -- setState
import React from 'react';
import {Button} from 'antd';

class SetState extends React.Component {

    state = { val: 0 }

    componentDidMount() {
        this.setState({ val: this.state.val + 1 })
       console.log('鉤子函數:', this.state.val) // 輸出的仍是更新前的值 --> 0
    }
    
    increment = () => {
        this.setState({ val: this.state.val + 1 })
        console.log('合成方法:', this.state.val) // 輸出的是更新前的val --> 1
    }

    render() {
        return (
            <Button type="primary" onClick={this.increment}>
                鉤子函數及合成方法 --> {`Counter is: ${this.state.val}`}
            </Button>
        )
    }
}

export default SetState;
  1. 原生js事件 -- setState
import React from 'react';
import {Button} from 'antd';

class SetState extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            val: 0
        }
    }

    componentDidMount() {
       document.body.addEventListener('click', this.changeValue, false)
    }

    changeValue = () => {
        this.setState({ val: this.state.val + 1 })
        console.log('原生js事件:', this.state.val) // 輸出的是更新後的值 --> 1
    }

    render() {
        return (
            <Button type="peimary">
                原生js事件 --> {`Counter is: ${this.state.val}`}
            </Button>
        )
    }
}

export default SetState;
  1. setTimeout 定時器 -- setState
import React from 'react';
import {Button} from 'antd';

class SetState extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            val: 0
        }
    }

    componentDidMount() {
        setTimeout(_ => {
            console.log('定時器->1:', this.state.val) // 輸出更新前的值 --> 0
            this.setState({ val: this.state.val + 1 })
            console.log('定時器->2:', this.state.val) // 輸出更新後的值 --> 1
        }, 0);
        console.log('鉤子函數:', this.state.val) // 輸出更新前的值 --> 0
    }

    render() {
        return (
            <Button type="primary">
                setTimeout 定時器 --> {`Counter is: ${this.state.val}`}
            </Button>
        )
    }
}

export default SetState;
  1. 批量更新 -- setState
import React from 'react';
import {Button} from 'antd';

class SetState extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            val: 0
        }
    }

    batchUpdates = () => {
        this.setState({ val: this.state.val + 1 })
        this.setState({ val: this.state.val + 1 })
        this.setState({ val: this.state.val + 1 })
    }

    render() {
        return (
            <Button type="primary" onClick={this.batchUpdates}>
                批量更新 --> {`Counter is: ${this.state.val}`}
            </Button>
        )
    }
}

export default SetState;
  1. 鉤子函數及 setTimmout 定時器 -- setState 的執行結果
import React from 'react';
import {Button} from 'antd';

class SetState extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            val: 0
        }
    }

    // 鉤子函數中的 setState 沒法立馬拿到更新後的值;
    // setState 批量更新的策略;
    // setTimmout 中 setState 是能夠同步拿到更新結果
    componentDidMount() {
        this.setState({ val: this.state.val + 1 })
        console.log('第一步->鉤子函數:', this.state.val); // 0
    
        this.setState({ val: this.state.val + 1 })
        console.log('第二步->鉤子函數:', this.state.val); // 0
    
        setTimeout(_ => {
            this.setState({ val: this.state.val + 1 })
            console.log('第三步->定時器:', this.state.val); // 2
        
            this.setState({ val: this.state.val + 1 })
            console.log('第四步->定時器:', this.state.val); //  3
        }, 0)
    }

    render() {
        return (
            <Button type="primary">
                鉤子函數及 setTimmout 定時器 --> {`Counter is: ${this.state.val}`}
            </Button>
        )
    }
}

export default SetState;

setState 第二個參數是一個回調函數

若是但願在 setState 時就能獲取到最新值,能夠在 setState 的回調函數中獲取最新結果源碼分析

this.setState(
    {
        data: newData
    },
    () => {
        console.log('回調函數中獲取:', me.state.data)
    }
);

總結

setState 所謂的 '異步',實際上只在合成事件一輩子命周期函數中存在,在原生js時間與定時器中任然是同步變化的, setState 中還作了批量更新的優化。若是但願在 this.setState({}) 後及時獲取到最新 state,能夠在其回調函數中獲取。

更多閱讀

相關文章
相關標籤/搜索