理解setState

近來在學習react源碼, 最初是直接從入口一行一行的看, 結果跟着調用的函數跳轉來跳去頭都暈了. 後來決定帶着一個目的去看源碼, 每次看只研究一個東西. 一開始最想了解的就是充滿魔性的setState. 本文是我對setState的一些理解, 不當之處歡迎留言指正.javascript

setState的魔性

看一下下邊幾個例子的輸出狀況.java

例1 合成事件中的setStatereact

import React from 'react';

export default class SetState extends React.Component {
    constructor(props) {
        super(props);
    }

    state = {
        count: 0
    }

    click = () => {
        this.setState({
            count: this.state.count + 1,
        })
        console.log('count1', this.state.count);
        this.setState({
            count: this.state.count + 1,
        });
        console.log('count2', this.state.count);
    }

    render() {
        return (
            <div onClick={this.click}>
                count的值{this.state.count}
            </div>
        )
    }
}
// 打印:
// count1 0
// count2 0

例2 生命週期函數中的setStatedom

import React from 'react';

export default class SetState extends React.Component {
    constructor(props) {
        super(props);
    }

    state = {
        count: 0
    }
    
    componentDidMount () {
        this.setState({
            count: this.state.count + 1,
        })
        console.log('count1', this.state.count);
        this.setState({
            count: this.state.count + 1,
        });
        console.log('count2', this.state.count);
    }

    render() {
        return (
            <div>
                count的值{this.state.count}
            </div>
        )
    }
}
// 打印:
// count1 0
// count2 0

例3 setTimeout中的setState異步

import React from 'react';

export default class SetState extends React.Component {
    constructor(props) {
        super(props);
    }

    state = {
        count: 0
    }

    componentDidMount () {
        setTimeout(() => {
            this.setState({
                count: this.state.count + 1,
            })
            console.log('count1', this.state.count);
            this.setState({
                count: this.state.count + 1,
            });
            console.log('count2', this.state.count);
        }, 0);
    }

    render() {
        return (
            <div>
                count的值{this.state.count}
            </div>
        )
    }
}
// 打印:
// count1 1
// count2 2

例4 Promise中的setState函數

import React from 'react';

export default class SetState extends React.Component {
    constructor(props) {
        super(props);
    }

    state = {
        count: 0
    }

    componentDidMount () {
        Promise.resolve()
        .then(() => {
            this.setState({
                count: this.state.count + 1,
            })
            console.log('count1', this.state.count);
            this.setState({
                count: this.state.count + 1,
            });
            console.log('count2', this.state.count);
        })
    }

    render() {
        return (
            <div>
                count的值{this.state.count}
            </div>
        )
    }
}
// 打印:
// count1 1
// count2 2

從例1和例2的輸出結果來看, 在setState後直接取state的值發現並無更新, setState對state的更新彷佛是個異步的過程; 學習

而從例3, 例4輸出結果來看, setState又是一個同步更新state的操做, 能夠當即拿到更新的結果. this

也就是說, setState有的時候是異步的有的時候是同步的, 真是很是的魔性. 根據網上的一些文章和本身的實驗能夠得出以下結論.spa

  • 在合成事件, 生命週期函數中的setState是異步批量更新的, 不能當即拿到更新的結果, 屢次setState只會走一次render
  • 在setTimeOut, setInterval, 原生事件, Promise中的setState是同步逐個更新的, 能夠當即拿到更新的state, 並且每次setState都會走一次render

關因而批量更新仍是非批量更新能夠在render函數中打印查看code

setState魔性表現揭祕

理解setState的異步批量更新

下邊是個異步批量更新的示意圖

理解setState的異步批量更新

這裏將在合成事件, setTimeout等中的寫的代碼的調用稱爲Main Process.

例以下邊componentDidMount中的代碼的執行都叫Main process.

componentDidMount () {
    this.setState({
        count: this.state.count + 1,
    });
    console.log('count1', this.state.count);
    this.setState({
        count: this.state.count + 1,
    });
    console.log('count2', this.state.count);
}

直接結合這段代碼分析上邊的這個看起來很牛x的圖.

首先執行一個setState, 判斷是setState操做, 建立一個更新任務加入更新隊列, 交給協調中心, 協調中心判斷不須要更新, 繼續執行main Process中的代碼.

遇到第一個console, 直接執行, 打印時取出了state, 顯然state沒更新仍是原來的值, 而後再執行Main Process代碼.

遇到第二個setState, 注意此時取出的state是沒有更新的, 再建立一個更新任務到更新隊列, 交給協調中心, 協調中心判斷不須要更新, 繼續執行main Process中的代碼. 而後執行了console, 取出的state是沒更新的.

必定時間後, 協調中心再次調度, 發現能夠更新了, 而後執行了更新隊列的兩個任務, 獲得一個新的state, 而後更新this.state和視圖.

從以上分析能夠了解到爲何兩個console打印的都是以前的值.

這裏有一個黑盒, 協調中心怎麼運行的, 這是之後須要研究的了, 目前尚不清楚, 能夠猜想這裏邊應該有個setTimeout 或者相似setTimeout的東西.

理解setState的同步單個更新

下邊是同步更新的示意圖

理解setState的同步逐個更新

這裏仍是結合一段代碼來分析

import React from 'react';

export default class SetState extends React.Component {
    constructor(props) {
        super(props);
    }

    state = {
        count: 0
    }

    click = () => {
        setTimeout(() => {
            this.setState({
                count: this.state.count + 1,
            })
            console.log('count1', this.state.count);
            this.setState({
                count: this.state.count + 1,
            });
            console.log('count2', this.state.count);
        }, 0);
    }
    
    render() {
        return (
            <div onClick={this.click}>
                count的值{this.state.count}
            </div>
        )
    }
}

首先遇到第一個setState, 判斷是setState, 建立一個更新任務到更新隊列, 而後進入協調中心, 協調中心經過某種手段判斷出須要同步更新, 直接執行更新隊列的任務, 獲得新的state, 而後更新視圖, 繼續執行Main Process中的代碼.

遇到console, 直接執行, 取出state(注意是更新了的)答應.

而後又遇到setState(注意這裏拿到的state是更新了的), 建立更新任務進入更新隊列, 而後進入協調中心, 協調中心經過某種手段判斷出須要同步更新, 直接執行更新隊列的任務, 獲得新的state, 而後更新視圖, 繼續執行Main Process中的代碼.

再次遇到console, 直接執行, 取出state(注意是二次了的)答應.

從以上分析能夠看出同步setState爲何是同步的, 緣由就在於他沒有一個異步判斷過程, 直接更新了state.

幾點待解決的問題

  • 協調中心是何時, 如何判斷出須要更新的
  • 協調中心是如何識別是一個setState是在setTimeout仍是在合成事件亦或生命週期等過程當中的.

彩蛋

說一下閱讀react源碼的感覺, 最開始直接看src目錄, react部分還行, 比較容易.

可是到了react-dom就不行了, 各類調用, 各類亂七八糟的東西, 有時跟着函數調用跳來跳去, 結果最開始想幹嗎的都忘了, 這樣讀起來真的很打擊人.

其實讀源碼更多不是瞭解其代碼組織方法, 而是瞭解核心原理.

下邊是幾個小建議:

  1. 帶着問題讀源碼, 尤爲是開始讀的時候, 若是漫無目的的讀, 會很沒有成就感, 甚至是強烈的挫敗感, 讀了半天也不知道學到了什麼
  2. react-dom 的src代碼組織十分複雜, 建議直接讀開發版的編譯產物, 都在一個文件裏, 比較容易找.
  3. 多用斷點, 能夠直接在開發版編譯產物打斷點看, 很是方便
  4. 不要糾結太多細節, 要抱有不求甚解的態度, 不懂的地方能夠暫時放過

小結

setState是一個容易讓人困惑的東西, 尤爲對react初學者來講, 可能感受有點琢磨不透. 本文結合源碼和本身的理解對setState的同步異步機制作了一些分析. 有些地方可能並非十分準確, 但但願能幫助對setState同步異步機制困惑的朋友理解一些其中的原理. 最後須要記憶一下什麼場景是同步更新, 什麼場景是異步更新, 這個是寫代碼能實實在在用的到的.

相關文章
相關標籤/搜索