setState详解
我们都知道,React通过this.state来访问state,通过this.setState()方法来更新state。当this.setState()方法被调用的时候,React会重新调用render方法来重新渲染UI
setState异步更新
setState方法通过一个队列机制实现state更新,当执行setState的时候,会将需要更新的state合并之后放入状态队列,而不会立即更新this.state(可以和浏览器的事件队列类比)。如果我们不使用setState而是使用this.state.key来修改,将不会触发组件的re-render。如果将this.state赋值给一个新的对象引用,那么其他不在对象上的state将不会被放入状态队列中,当下次调用setState并对状态队列进行合并时,直接造成了state丢失。(这里特别感谢@Dcatfly的指正)
我们来看一下React文档中对setState的说明
void setState(
function|object nextState,
[function callback]
)
The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered.
翻译一下,第二个参数是一个回调函数,在setState的异步操作结束并且组件已经重新渲染的时候执行。也就是说,我们可以通过这个回调来拿到更新的state的值。
React也正是利用状态队列机制实现了setState的异步更新,避免频繁地重复更新state(pending的意思是未定的,即将发生的)
//将新的state合并到状态更新队列中
var nextState = this._processPendingState(nextProps, nextContext);
//根据更新队列和shouldComponent的状态来判断是否需要更新组件
var shouldUpdate =
this._pendingForceUpdate ||
!inst.shouldComponentUpdate ||
inst.shouldComponentUpdate(nextProps, nextState, nextContext);
setState循环调用风险
当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pending-StateQueue更新队列进行合并操作,最终通过enqueueUpdate执行state更新
而performUpdateIfNecessary方法会获取_pendingElement,_ pendingStateQueue,_ pending-ForceUpdate,并调用receiveComponent和updateComponent方法进行组件更新
如果在shouldComponentUpdate或者componentWillUpdate方法中调用setState,此时this._pending-StateQueue != null,就会造成循环调用,使得浏览器内存占满后崩溃
调用栈
既然setState最终是通过enqueueUpdate执行state更新,那么enqueueUpdate到底是如何更新state的呢? 首先看下面的问题
import React, { Component } from 'react';
class Example extends Component {
constructor(){
super();
//在组件初始化可以直接操作this.state
this.state = {
val: 0
}
}
componentDidMount(){
this.setState({
val: this.state.val + 1
});
//第一次输出
console.log(this.state.val);
this.setState({
val: this.state.val + 1
});
//第二次输出
console.log(this.state.val);
setTimeout(()=>{
this.setState({val: this.state.val + 1});
//第三次输出
console.log(this.state.val);
this.setState({
val: this.state.val + 1
});
//第四次输出
console.log(this.state.val);
}, 0);
}
render(){
return null;
}
}
上述代码中,4次console.log打印出来的val分别是: 0,0,2 ,3
我们来看一个简化的setState的调用栈
this.setState(newState) =>
newState存入pending队列 =>
调用enqueueUpdate =>
是否处于批量更新模式 =>
是的话将组件保存到dirtyComponents
不是的话遍历dirtyComponents,调用updateComponent,更新pending state or props
enqueueUpdate的源码如下(上面流程的第三步)(batching的意思是批量的)
function enqueueUpdate(component){
//injected注入的
ensureInjected();
//如果不处于批量更新模式
if(!batchingStrategy.isBatchingUpdates){
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
//如果处于批量更新模式
dirtyComponents.push(component);
}
如果isBatchingUpdates为true,则对所有队列中的更新执行batchedUpdates方法,否则只把当前组件(即调用了setState的组件)放入dirtyComponents数组中,例子中4次setState调用的表现之所以不同,这里的逻辑判断起了关键作用
事务
事务就是将需要执行的方法使用wrapper封装起来,再通过事务提供的perform方法执行,先执行wrapper中的initialize方法,执行完perform之后,在执行所有的close方法,一组initialize及close方法称为一个wrapper。
那么事务和setState方法的不同表现有什么关系,首先我们把4次setState简单归类,前两次属于一类,因为它们在同一调用栈中执行,setTimeout中的两次setState属于另一类。
在setState调用之前,已经处在batchedUpdates执行的事务中了。那么这次batchedUpdates方法是谁调用的呢,原来是ReactMount.js中的_renderNewRootComponent方法。也就是说,整个将React组件渲染到DOM中的过程就是处于一个大的事务中。而在componentDidMount中调用setState时,batchingStrategy的isBatchingUpdates已经被设为了true,所以两次setState的结果没有立即生效。
再反观setTimeout中的两次setState,因为没有前置的batchedUpdates调用,所以导致了新的state马上生效。
引用原文:http://blog.csdn.net/sysuzhyupeng/article/details/63250741
写博客是为了记住自己容易忘记的东西,另外也是对自己工作的总结,文章可以转载,无需版权。希望尽自己的努力,做到更好,大家一起努力进步!
如果有什么问题,欢迎大家一起探讨,代码如有问题,欢迎各位大神指正!
setState详解的更多相关文章
- 深入React技术栈之setState详解
抛出问题 class Example extends Component { contructor () { super() this.state = { value: 0, index: 0 } } ...
- Lock的实现之ReentrantLock详解
摘要 Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作: 虽然锁有很多实现,但是都依赖AbstractQueuedSynchroniz ...
- [转] ReactNative Animated动画详解
http://web.jobbole.com/84962/ 首页 所有文章 JavaScript HTML5 CSS 基础技术 前端职场 工具资源 更多频道▼ - 导航条 - 首页 所有文章 ...
- AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- 【Java并发】详解 AbstractQueuedSynchronizer
前言 队列同步器 AbstractQueuedSynchronizer(以下简称 AQS),是用来构建锁或者其他同步组件的基础框架.它使用一个 int 成员变量来表示同步状态,通过 CAS 操作对同步 ...
- 详解 Node + Redux + MongoDB 实现 Todolist
前言 为什么要使用 Redux? 组件化的开发思想解放了繁琐低效的 DOM 操作,以 React 来说,一切皆为状态,通过状态可以控制视图的变化,然后随着应用项目的规模的不断扩大和应用功能的不断丰富, ...
- Java经典设计模式之十一种行为型模式(附实例和详解)
Java经典设计模式共有21中,分为三大类:创建型模式(5种).结构型模式(7种)和行为型模式(11种). 本文主要讲行为型模式,创建型模式和结构型模式可以看博主的另外两篇文章:Java经典设计模式之 ...
- Java并发之AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- Netty4.x整合SpringBoot2.x使用Protobuf3详解
前言 本篇文章主要介绍的是SpringBoot整合Netty以及使用Protobuf进行数据传输的相关内容.Protobuf会介绍下用法,至于Netty在netty 之 telnet HelloWor ...
随机推荐
- linux中的etc目录
etc不是什么缩写,是and so on的意思 来源于 法语的 et cetera 翻译成中文就是 等等 的意思. 至于为什么在/etc下面存放配置文件, 按照原始的UNIX的说法(linux文件结构 ...
- call to unavailable function system not available on ios 解决方案
编译时报错:call to unavailable function system not available on iOS 原因:iOS11已经将system删除 解决方案:system(comma ...
- 《ASP.NET 1200例》ref关键字与out关键字
REF关键字 ref 关键字会导致通过引用传递的参数,而不是值. 通过引用传递的效果是在方法中对参数的任何改变都会反映在调用方的基础参数中. 引用参数的值与基础参数变量的值始终是一样的. 不要将“通过 ...
- java pdf 导出方案
java代码 import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.BaseFont; import org ...
- 【APIO2014】Palindromes
#103. [APIO2014]Palindromes 统计 描述 提交 自定义测试 给你一个由小写拉丁字母组成的字符串 ss.我们定义 ss 的一个子串的存在值为这个子串在 ss 中出现的次数乘以这 ...
- git 四个基本对象、分支、三个存储区、reset-revert-变基、cherry-pick
1:git四个基本对象 2:工作区.缓存去.历史区 3:Git 分支介绍 https://blog.csdn.net/wh_19910525/article/details/7470964 ...
- Oracle存储——逻辑结构
Oracle 数存储——物理结构 Oracle存储结构:物理结构+逻辑结构 Oracle 数据库存储逻辑结构 Oracle Schema Objects(Schema Object Storage A ...
- 微信公众号 待发货-物流中-已收货 foreach break continue
w <?php $warr = array(1,2,3); $w_break = 0; foreach($warr AS $w){ if($w==2)break; $w_break += $w; ...
- IO 流之字符流的缓冲区
缓冲区的出现提高了对数据的读写效率 对应类: BufferedWriter BufferedReader 缓冲区需要结合流才可以使用, 对流的功能进行了增强, 即对流的操作起到装饰作用 使用缓冲区实现 ...
- pycharm中选择python interpreter
pycharm中选择python interpreter pycharm中有两处地方需要选择python解释器: 一处是调试配置(edit configurations)处,这里选择python解释器 ...