在看React的官方文档的时候, 发现了这么一句话,State Updates May Be Asynchronous,于是查询了一波相关的资料, 最后归纳成以下3个问题

  1. setState为什么要异步更新,它是怎么做的?
  2. setState什么时候会异步更新, 什么时候会同步更新?
  3. 既然setState需要异步更新, 为什么不让用户可以同步读到state的新值,但更新仍然是异步?

常见场景下的异步更新

以下是官方文档的一个例子, 调用了3次incrementCount方法, 期望this.state.count的值是3, 但最后却是1

incrementCount() {
this.setState({count: this.state.count + 1});
} handleSomething() {
// Let's say `this.state.count` starts at 0.
this.incrementCount();
this.incrementCount();
this.incrementCount();
// When React re-renders the component, `this.state.count` will be 1, but you expected 3. // This is because `incrementCount()` function above reads from `this.state.count`,
// but React doesn't update `this.state.count` until the component is re-rendered.
// So `incrementCount()` ends up reading `this.state.count` as 0 every time, and sets it to 1. // The fix is described below!
}

那么就可以引出第一个问题

setState为什么要异步更新,它是怎么做的?

深入源码你会发现:(引用程墨老师的setState何时同步更新状态)

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

然后我在网上引用了这张图(侵删)

从结论和图都可以得出, setState是一个batching的过程, React官方认为, setState会导致re-rederning, 而re-rederning的代价是昂贵的, 所以他们会尽可能的把多次操作合并成一次提交。以下这段话是Dan在Issue中的回答:

中心意思大概就是:

同步更新setState并re-rendering的话在大部分情况下是无益的, 采用batching会有利于性能的提升, 例如当我们在浏览器插入一个点击事件时,父子组件都调用了setState,在batching的情况下, 我们就不需要re-render两次孩子组件,并且在退出事件之前re-render一次即可。

那么如果我们想立即读取state的值, 其实还有一个方法, 如下代码:

因为当传入的是一个函数时,state读取的是pending队列中state的值

incrementCount() {
this.setState((state) => {
// Important: read `state` instead of `this.state` when updating.
return {count: state.count + 1}
});
} handleSomething() {
// Let's say `this.state.count` starts at 0.
this.incrementCount();
this.incrementCount();
this.incrementCount(); // If you read `this.state.count` now, it would still be 0.
// But when React re-renders the component, it will be 3.
}

当然, 仔细看React文档的话, 可以发现, State Updates May Be Asynchronou里面有一个may的字眼,也就是可能是异步更新, 因而引出第二个问题

setState什么时候会异步更新, 什么时候会同步更新?

其实从第一个问题中我们就知道,React是根据isBatchingUpdates来合并更新的, 那么当调用setState的方法或者函数不是由React控制的话, setState自然就是同步更新了。

简单的举下例子:

  1. 如componentDidMount等生命周期以及React的事件即为异步更新,这里不显示具体代码。
  2. 如自定义的浏览器事件,setTimeout,setInterval等脱离React控制的方法, 即为同步更新, 如下(引用程墨老师的setState何时同步更新状态)
componentDidMount() {
document.querySelector('#btn-raw').addEventListener('click', this.onClick);
}
onClick() {
this.setState({count: this.state.count + 1});
console.log('# this.state', this.state);
}
// ......
render() {
console.log('#enter render');
return (
<div>
<div>{this.state.count}
<button id="btn-raw">Increment Raw</button>
</div>
</div>
)
}

有的人也会想能不能React依然合并更新, 但用户可以同步读取this.state的值, 这个问题在React的一个Issue上有提到, 也是我们的第三个问题

既然setState需要异步更新, 为什么不让用户可以同步读到state的新值,但更新仍然是异步?

这个问题可以直接在Dan的回答中得到:

This is because, in the model you proposed, this.state would be flushed immediately but this.props wouldn’t.
And we can’t immediately flush this.props without re-rendering the parent, which means we would have to give up on batching (which, depending on the case, can degrade the performance very significantly).

大概意思就是说:

如果在应用中,this.state的值是同步,但是this.props却不是同步的。因为props只有当re-rendering父组件后才传给子组件,那么如果要props变成同步的, 就需要放弃batching。 但是batching不能放弃。

关于setState的一些记录的更多相关文章

  1. 7.ReadWriteLock接口及其实现ReentrantReadWriteLock

    Java并发包的locks包里的锁基本上已经介绍得差不多了,ReentrantLock重入锁是个关键,在清楚的了解了同步器AQS的运行机制后,实际上再分析这些锁就会显得容易得多,这章节主讲另外一个重要 ...

  2. Taro踩坑记录一: swiper组件pagestate定制,swiperChange中setState导致组件不能滚动。

    import Taro, { Component } from '@tarojs/taro'; import { Swiper, SwiperItem, Image, View } from '@ta ...

  3. React-native 学习记录

    在此记录下学习中的小知识 今天在componentWillUpdate调用this.setState方法,想达到一个效果:就像viewWillAppear时,系统从网络请求新的数据,并刷新界面, 但是 ...

  4. react 源码之setState

    今天看了react源码,仅以记录. 1:monorepo (react 的代码管理方式) 与multirepo 相对. monorepo是单代码仓库, 是把所有相关项目都集中在一个代码仓库中,每个mo ...

  5. Java设计模式学习记录-状态模式

    前言 状态模式是一种行为模式,用于解决系统中复杂的对象状态转换以及各个状态下的封装等问题.状态模式是将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象的状态可以灵活多变.这样在客户端使 ...

  6. react-route4 学习记录

    新建项目 create-react-app react20180413 安装路由 npm install react-router-dom -S 跑通路由 删除全部文件后 重新新建index.js 代 ...

  7. 记录一次react相关总结

    背景说明:元旦接到一个管理后台的项目,是一个关于自定义专题的项目.通过后台的配置自定义专题,前端根据专题模块进行渲染数据.由于管理后台是react做的前后分离,对于一个后端的我来说写写js也算是浅尝一 ...

  8. react + antiDesign开发中遇到的问题记录

    react + antiDesign开发中遇到的问题记录 一:页面中子路由失效: antiDesign的官方实例中,会把路由重复的地方给去重,而且路由匹配模式不是严格模式.所以我们需要在util.js ...

  9. 17-7-25-js记录

    先说明下为什么说好每天一更,周五周六周日都没有更新.因为在周五的时候,上司主动找我谈了转正后的工资4-4.5K.本来想好是6K的,后来打听了一圈公司的小伙伴,都是5-5.5,我就把自己定到了5K.万万 ...

随机推荐

  1. react.JS基础

    1.ReactDOM.render() React.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点. <!DOCTYPE html> ...

  2. 使用POST下载文件

    一直以来,JS都没有比较好的可以直接处理二进制的方法.而Blob的存在,允许我们可以通过JS直接操作二进制数据.一.下载util.fetchDownload= function (opt,data) ...

  3. Windows下编译jcef

    依赖软件参考 本文参考官方网站上的jcef编译过程 编译成功的环境如下: windows 10 64 bit JDK 1.8.0_121 64 bit Python 2.7.13 git versio ...

  4. iview安装使用

    iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品. 安装 cd 项目 cnpm install iview -S 在项目中引入iview 在入口文件mai ...

  5. hive的join

    第一:在map端产生join          mapJoin的主要意思就是,当链接的两个表是一个比较小的表和一个特别大的表的时候,我们把比较小的table直接放到内存中去,然后再对比较大的表格进行m ...

  6. javascript的数组之reverse()

    reverse()方法将数组中所有元素的位置颠倒,修改原数组,并返回一个原数组的引用. var array1 = ['one', 'two', 'three']; var reversed = arr ...

  7. Python实现实现基于最小二乘法的线性回归

    下面展示利用Python实现基于最小二乘法的线性回归模型,同时不需要引入其他科学计算以及机器学习的库. 利用Python代码表示如下: #首先引入数据集x,和y的值的大小利用Python的数据结构:列 ...

  8. [Swift]LeetCode159.具有最多两个不同字符的最长子串 $ Longest Substring with At Most Two Distinct Characters

    Given a string S, find the length of the longest substring T that contains at most two distinct char ...

  9. [Swift]LeetCode1027. 最长等差数列 | Longest Arithmetic Sequence

    Given an array A of integers, return the length of the longest arithmetic subsequence in A. Recall t ...

  10. iOS学习——图片压缩到指定大小以内

    一.图片压缩简述 在我们开发过程中,有可能会遇到拍照.或者从相册中选择图片,要么单选或者多选,然后上传图片到服务器,一般情况下一张图片可能3-4M,如果类似微信朋友圈上传9张图片大约是 35M左右,如 ...