React中setState的怪异行为 ——setState没有即时生效
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); // 输出0
this.setState({count: this.state.count + 1});
console.log(this.state.count); // 输出0
setTimeout(() => {
this.setState({count: this.state.count + 1});
console.log(this.state.count); // 输出2
this.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 allocations
if (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没有即时生效的更多相关文章
- (文章也有问题,请自行跳过)react中的状态机每次setState都是重新创建新的对象,如需取值,应该在render中处理。
demo如下 class Demo4StateLearn extends React.Component { constructor(props) { super(props); this.state ...
- React 中的this.setState
在react中如何修改state中的数据 第一种写法:this.setState() 参数1:对象 需要修改的数据 参数2:回调 this.setState是一 ...
- React中this.setState是同步还是异步?为什么要设计成异步?
在使用react的时候,this.setState为什么是异步呢? 一直以来没有深思这个问题.昨天就此问题搜索了一下. react创始人之一 Dan Abramovgaearon在GitHub上回答了 ...
- react中的setState的使用和深入理解
前端框架从MVC过渡到MVVM.从DOM操作到数据驱动,一直在不断的进步着,提升着, angular中用的是watcher对象,vue是观察者模式,react就是state了,他们各有各的特点,没有好 ...
- React中setState学习总结
react中setState方法到底是异步还是同步,其实这个是分在什么条件下是异步或者同步. 1.先来回顾一下react组件中改变state的几种方式: import React, { Compone ...
- React中的setState到底发生了什么?
https://yq.aliyun.com/ziliao/301671 https://segmentfault.com/a/1190000014498196 https://blog.csdn.ne ...
- React中setState如何修改深层对象?
在React中经常会使用到setState,因为在react生态中,state就是一切.在开发过程中,时长会在state中遇到一些比较复杂的数据结构,类似下面这样的: 这时需要我们修改list中obj ...
- 3.React中的setstate的几个现象
转载segfault 上面的一篇文章,https://segmentfault.com/a/1190000014498196 1.在同一个方法中多次setState是会被合并的,并且对相同属性的设置只 ...
- react中setState用法
setState()更新状态的2种写法 setState(updater, [callback]), updater为返回stateChange对象的函数: (state, props) => ...
随机推荐
- springboot集成elk 四:springboot + Elasticsearch+Jest
依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spri ...
- 提车应该检查哪?4S店都怕你检查这4个“雷区”,别等后悔才知道
https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9381645601643691163% ...
- IdentityServer4 学习一
网上找的关于IdentityServer4的百度脑图 http://naotu.baidu.com/file/75b251257ce27cfa62e0ad7f47b75576?token=e2db61 ...
- 第7章学习小结 不使用STL-map过实践题:QQ帐户的申请与登陆
目录: 一:查找的概念与术语 二:折半查找 三:二叉排序树 四:平衡二叉树 五:B-树 六:B+树 七:散列表 八:实践题:QQ帐户的申请与登陆 九:自我总结 一.查找的概念与术语 (一)查找表 查找 ...
- git使用中的一些命令及心得
Git 与 SVN 区别点: 1.Git 是分布式的,SVN 不是:这是 Git 和其它非分布式的版本控制系统,例如 SVN,CVS 等,最核心 的区别. 2.Git 把内容按元数据方式存储,而 SV ...
- 选择排序——C语言
选择排序 1.算法描述 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾.以此类推,直到所有元素均排序完毕(放 ...
- 编码方式之ASCII、ANSI、Unicode概述
1.ASCII ASCII全称(American Standard Code for Information Interchange)美国信息交换标准代码,在计算机内部中8位二进制位组成1个字节(8( ...
- WinRAR 去广告的姿势
一直在使用WinRAR解压文件,感觉非常的好用,可是现在WinRAR添加了广告,每次打开压缩包都会弹出广告,有时候甚至在解压的时候弹出来,而每次弹出广告都会卡顿一下,忍了很长时间今天实在是受够了,准备 ...
- Core使用SAP Web Service
.Net Core在使用SAP的Web Service会遭遇到一些错误,貌似目前并不支持SAP中的Web Service,我们需要曲线实现下调用过程: 经测试,不再需要Framework项目中转,Sy ...
- PHP迭代生成器---yield
1.迭代生成器 生成器的核心是一个yield关键字,一个生成器函数看起来像一个普通的函数,不同的是:普通函数返回一个值,而一个生成器可以yield生成许多它所需要的值.生成器函数被调用时,返回的是一个 ...