setState 简单整理

好久没写东西了。。。。
来头条之后, 每天身体被掏空。也没多少时间写东西,
今天周末,上午没啥事, 简单写写,


一个例子

假如有这样一个点击执行累加场景:

// ...
  this.state = {
    count: 0,
  }

  incrementCount() {
    this.setState({count: this.state.count + 1});
  }

  handleIncrement = () => {
    this.incrementCount();
    this.incrementCount();
    this.incrementCount();
  }


 <button ref="button" onClick={this.handleIncrement}>点击</button>
// ...

每一次点击, 会执行三次累加,看一下输入:


image.png

并没有达到预期的效果,纠正也很简单:

incrementCount() {
  this.setState((prevState) => {
    return {count: prevState.count + 1}
  });
}

再看输出:


image.png

setState 的时候, 一个传入了object, 一个传入了更新函数。
区别在于: 传入一个更新函数,就可以访问当前状态值。 setState调用是批量处理的,因此可以让更新建立在彼此之上,避免冲突。

那问题来了, 为什么前一种方式就不行呢? 具体一点说, setState为什么不会同步更新组件状态呢? 带着这个疑问,继续往下看。

进入这个问题之前,我们先回顾一下现在对setState的认知:

  1. setState不会立刻改变React组件中state的值.
  2. setState通过触发一次组件的更新来引发重绘.

重绘指的就是引起React的更新生命周期函数4个函数:

  • shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)
  • componentWillUpdate(被调用时this.state没有更新)
  • render(被调用时this.state得到更新)
  • componentDidUpdate
  1. 多次setState函数调用产生的效果会合并。

如果每一次setState调用都走一圈生命周期,光是想一想也会觉得会带来性能的问题,其实这四个函数都是纯函数,性能应该还好,但是render函数返回的结果会拿去做Virtual DOM比较和更新DOM树,这个就比较费时间。

目前React会将setState的效果放在队列中,积攒着一次引发更新过程,为的就是把Virtual DOM和DOM树操作降到最小,用于提高性能。

查阅一些资料后发现,某些操作还是可以同步更新this.state的。
直接说结论吧:

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。

所谓“除此之外”,指的是绕过React通过addEventListener直接添加的事件处理函数,还有通过setTimeout/setInterval产生的异步调用。

https://jsbin.com/mavolejufi/edit?html,js,console,output

盗用一张图:

setState 过程

在React的setState函数实现中,会根据一个变量isBatchingUpdates判断是直接更新this.state还是放到队列中。
而isBatchingUpdates默认是false,也就表示setState会同步更新this.state,但是有一个函数batchedUpdates,这个函数会把isBatchingUpdates修改为true,而当React在调用事件处理函数之前就会调用这个batchedUpdates,造成的后果,就是由React控制的事件处理过程setState不会同步更新this.state。

知道上面的一些理论之后, 我们再看一个例子就很清晰了:

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return null;
  }
};

毫无疑问输出 0 0 2 3 ;

前两次在isBatchingUpdates 中,不用更新state, 输出两个0。
后面两次会同步更新, 分别输出2, 3;

上面的例子,我们就知道了setState 是可以同步更新的,但是还是尽量避免直接使用, 仅作了解用。
如果你非要玩一些骚操作,写出这样的代码去直接去操作this.state:

    this.state.count = this.state.count + 1;
    this.state.count = this.state.count + 1;
    this.state.count = this.state.count + 1;
    this.setState();

我只能说, 大胸弟, 你很骚。吾有旧友叼似汝,而今坟草丈许高。

根据以上内容, 简单重复下结论:

  • 不要直接去操作this.state, 这样会造成不必要的性能更问题和隐患。
  • 由React引发的事件处理,调用setState不会同步更新this.state,除此之外的setState调用会同步执行this.state。

就简单写到这吧, 要出门了 :)。

扩展阅读:
https://reactjs.org/docs/faq-state.html#what-does-setstate-do

关于事务,还有相关源码的问题可以参考:
https://zhuanlan.zhihu.com/p/20328570

https://zhuanlan.zhihu.com/p/25882602
https://zhuanlan.zhihu.com/p/26069727
https://zhuanlan.zhihu.com/p/25990883
https://reactjs.org/docs/faq-state.html#what-does-setstate-do
https://reactjs.org/docs/react-component.html#setstate

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容