setState可以说是React中使用频率最高的一个函数了,我们都知道,React是通过管理状态来实现对组件的管理的,当this.setState()被调用的时候,React会重新调用render方法来重新渲染UI

但实际使用的时候,我们会发现,有时候我们setState之后,并没有立刻生效,例如我们看一下以下的示例代码

class Test extends Component {constructor(props) {super(props);this.state = {count: 0};}componentDidMount() {this.setState({count: this.state.count + 1});console.log(this.state.count); // 输出0this.setState({count: this.state.count + 1});console.log(this.state.count); // 输出0setTimeout(() => {this.setState({count: this.state.count + 1});console.log(this.state.count); // 输出2this.setState({count: this.state.count + 1});console.log(this.state.count); // 输出3}, 0);}render() {return <div> test </div>;}
}

开发过程中我们会发现,在componentDidMount方法中,我们调用setState之后state的值并没有立即改变,但如果我们在setTimeOut里面调用,我们却能立刻就能获得更新,原因就在于react中的使用了基于事务(传送门,关于事务原理的解析)的异步更新机制,但对于这个异步的理解,又跟ajax的异步有所不同,因为毕竟react是一个js框架,所有的操作都是单线程的,所有的操作,都得按顺序来,那么它具体是怎么实现的呢?

我们都知道,对于dom的操作对性能的损耗是非常严重的,所以react为了提高整体的渲染性能,会将一次渲染周期中的state进行合并,在这个渲染周期中你对所有setState的所有调用都会被合并起来之后,再一次性的渲染,这样可以避免频繁的调用setState导致频繁的操作dom,提高渲染性能。具体的实现方面,可以简单的理解为react中存在一个状态变量isBatchingUpdates,当处于渲染周期开始时,这个变量会被设置成true,渲染周期结束时,会被设置成false,react会根据这个状态变量,当出在渲染周期中时,仅仅只是将当前的改变缓存起来,等到渲染周期结束时,再一次性的全部render,,具体的流程可以参照下面的流程图

现在,我们回到最开始的问题,为什么一开始在componentDidMount中直接执行setState会无法立刻得到更新呢,原因就在于,我们在componentDidMount中其实处于首次渲染的事务当中,这次事务的渲染尚未完成,而首次渲染的时候会将isBatchingUpdates设置为true,这是我们在componentDidMount中调用setState,react会发现当前事务尚未完成,只会直接将修改后的state放入到dirtyComponents中,等待最终渲染周期完成时,将所有的state进行合并,一次性render。而当我们放在setTimeOut里面的时候,setTimeOut会将操作放到执行队列的最后方,也就是说会等待渲染周期结束之后再进行setState,这个时候状态变量已经被重置回来了,所以此时我们的每一次setState都会立刻生效

接下来,我们从源码的角度来看看setState是怎么操作的

function enqueueUpdate(component) {ensureInjected();//不在渲染周期中if (!batchingStrategy.isBatchingUpdates) {batchingStrategy.batchedUpdates(enqueueUpdate, component);return;}//渲染周期中,直接缓存state等待下一步更新dirtyComponents.push(component);
}

这个逻辑跟上图的逻辑是一样的,当我们调用setState函数的时候,实际上最终会调用到enqueueUpdate函数,整体逻辑上面已经分析过了,就不再赘述,接下来看看setState是如何通过事务来进行渲染的,也就是batchingStrategy.batchedUpdates到底做了些什么,往下走之前,如果对事务不了解建议先看看这篇文章(传送门,关于事务原理的解析)

var ReactUpdates = require('ReactUpdates');
var Transaction = require('Transaction');var emptyFunction = require('emptyFunction');//第二个wrapper
var RESET_BATCHED_UPDATES = {initialize: emptyFunction,close: function() {ReactDefaultBatchingStrategy.isBatchingUpdates = false;},
};//第一个wrapper
var FLUSH_BATCHED_UPDATES = {initialize: emptyFunction,close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates),
};//wrapper列表
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];//事务构造函数
function ReactDefaultBatchingStrategyTransaction() {
//原型中定义的初始化方法this.reinitializeTransaction();
}//继承原型
Object.assign(ReactDefaultBatchingStrategyTransaction.prototype,Transaction.Mixin,{getTransactionWrappers: function() {return TRANSACTION_WRAPPERS;},}
);//新建一个事务
var transaction = new ReactDefaultBatchingStrategyTransaction();var ReactDefaultBatchingStrategy = {isBatchingUpdates: false,batchedUpdates: function(callback, a, b, c, d, e) {var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;ReactDefaultBatchingStrategy.isBatchingUpdates = true;// The code is written this way to avoid extra allocationsif (alreadyBatchingUpdates) {callback(a, b, c, d, e);} else {//在这个地方调用事务,callback是从外部传入的方法transaction.perform(callback, null, a, b, c, d, e);}},
};

上面这个就是react渲染所使用的事务,react就是用这个事务来处理setState引起的渲染,根据我们刚刚的解释,我们可以看到,事务开始时就把isBatchingUpdates设置成了true,防止在一次渲染周期中重复渲染,我们还可以看到这个事务定义了两个wrapper,其出口方法close分别用于执行渲染和设置状态变量,而执行渲染的FLUSH_BATCHED_UPDATES 要先于执行设置状态变量的RESET_BATCHED_UPDATES ,也就是说,执行渲染之后,才会通过RESET_BATCHED_UPDATES的close方法执行这句代码

ReactDefaultBatchingStrategy.isBatchingUpdates = false;

之后整个渲染周期结束。这时候当我们执行setState的时候,重新进入一个新的渲染周期

那么,问题来了,当我们在渲染周期中执行了setState之后,我们要如何获取到最新的state的值呢,setTimeOut是一个方案,但是不太优雅,有没有其他方法呢,我们注意到,setState提供了一个回调函数,我们只需要在回调里面获取更新后的state即可,像这样

componentDidMount() {this.setState({count: this.state.count + 1},()=>{console.log(this.state.count);//该是啥就是是啥}));}

React中setState的怪异行为 ——setState没有即时生效相关推荐

  1. React学习(6)-React中组件的数据-state

    前言 组件中的state具体是什么?怎么更改state的数据? setState函数分别接收对象以及函数有什么区别? 如何划分组件的状态数据,进行自我的灵魂拷问,以及props与state的灵魂对比 ...

  2. React学习(六)-React中组件的数据-state

    虽互不曾谋面,但希望能和你成为笔尖下的朋友 以读书,技术,生活为主,偶尔撒点鸡汤 不作,不敷衍,意在真诚吐露,用心分享 点击左上方,可关注本刊 撰文 | 川川 ID:suibichuanji 点击下方 ...

  3. 关于react中setState的深入理解

    在react中,通常通过state或props,来控制整个组件的状态.一旦state或props发生改变,整个组件会被重新渲染.在setState的理解上,可能会存在一些误区. setState的基本 ...

  4. React中setState修改深层对象

    在React中经常会使用到setState,因为在react生态中,state就是一切.在开发过程中,时长会在state中遇到一些比较复杂的数据结构,类似下面这样的: state = {list: { ...

  5. [react] react中的setState执行机制是什么呢?

    [react] react中的setState执行机制是什么呢? 1.将setState传入的partialState参数存储在当前组件实例的state暂存队列中. 2.判断当前React是否处于批量 ...

  6. [react] react中的setState缺点是什么呢?

    [react] react中的setState缺点是什么呢? 调用时机不恰当的话可能引起循环调用的问题:比如在componentWillUpdate render componentDidUpdate ...

  7. [react] react中的setState和replaceState的区别是什么?

    [react] react中的setState和replaceState的区别是什么? setState:更新状态 replaceState:替换状态 个人简介 我是歌谣,欢迎和大家一起交流前后端知识 ...

  8. [react] react中的setState是同步还是异步的呢?为什么state并不一定会同步更新?

    [react] react中的setState是同步还是异步的呢?为什么state并不一定会同步更新? 由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state . ...

  9. [react] react中setState的第二个参数作用是什么呢?

    [react] react中setState的第二个参数作用是什么呢? 第二个参数是一个callback函数,用于setState设置state的属性值成功之后的回调,此时调用this.state.p ...

  10. [react] react中调用setState会更新的生命周期有哪几个?

    [react] react中调用setState会更新的生命周期有哪几个? (>=16.4之后,与props的更新一致, <16.4的版本没有static getDerivedStateF ...

最新文章

  1. WR:城环所杨军组揭示亚热带水库浮游有壳虫群落构建受随机性过程影响比确定性过程更强...
  2. 微信小程序项目重构之Redux状态管理
  3. 让IE8和IE9支持 placeholder
  4. 微信小程序,时间戳和日期格式互相转化
  5. UPS及蓄电池的运行管理、维护操作和常见故障处理
  6. boost::graph模块实现Graphviz DOT 语言阅读器
  7. Telltale:看Netflix如何简化应用程序监控体系
  8. 计算机网络PIC和SDV,SRBSDV和RBSDV检测技术的建立
  9. 使用Jexus 容器化您的 Blazor 应用程序
  10. 文件读取ini文件另一种读取办法
  11. linux程序默认启动项,windows和linux双系统,设置默认启动项
  12. linux信任本地jar包,jar包在linux本地运行成功, 但是jenkins构建失败
  13. node访问oracledb的环境搭建
  14. C# USING ADO.NET
  15. Eclipse快速导入继承类的方法
  16. redis的运行机制
  17. 龙芯2k1000-pmon(5)- pmon无法修改环境变量的问题
  18. multisim常用d触发器_怎么在multisim找D触发器
  19. MySQL使用JDBC高级操作和事务
  20. 蓝屏死机(blue screen of death)

热门文章

  1. 项目升级与环境变化架构侧重点
  2. mysql允许连接表为空_mysql – 选择一个表中的所有项并与另一个表连接,允许空值...
  3. 移动apn接入点哪个快_2020年北京移动电信联通校园卡5G网速究竟有多快?
  4. dfuse——最顺畅的区块链 API 现已接入 Worbli
  5. 用 dfuse `transaction_lifecycle` 端点跟踪任何交易,包括延期交易
  6. 酷狗如何打造集听、看、玩、唱于一体的泛娱乐音乐平台?
  7. SAP开发入门-ABAP
  8. C++ 常指针和指向常量的指针
  9. Android Stidio修改桌面图标
  10. sqlserver只剩mdf数据文件如何附加