理解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同步异步机制困惑的朋友理解一些其中的原理. 最后须要记忆一下什么场景是同步更新, 什么场景是异步更新, 这个是写代码能实实在在用的到的.

相关文章
相关标签/搜索