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 ...
随机推荐
- SQL Server快捷方式丢了怎么启动
为了帮助网友解决“SQL Server快捷方式丢了怎么启动”相关的问题,中国学网通过互联网对“SQL Server快捷方式丢了怎么启动”相关的解决方案进行了整理,用户详细问题包括:Microsoft ...
- c语言加密算法头文件下载(base64、md5、sha1)
1.base64 网上有一份开头就是 // Encoding lookup table char base64encode_lut[] = { 的base64.h, 在loadrunner中加密有b ...
- Kotlin——高级篇(五):集合之常用操作符汇总
在上一篇文章Kotlin--高级篇(四):集合(Array.List.Set.Map)基础中讲解到了数组Array<T>.集合(List.Set.Map)的定义与初始化.但是由于篇幅的原因 ...
- Win10-64位 免安装版Mysql8下载安装运行
今天忙活了很久去下载安装Mysql,感觉网上的那些教程怎么都对不上呢,很奇怪,不过我乱点一通至少能用了,先凑和着用吧... 记录一下, 要是不对的,以后再修改...windows10系统 2018-5 ...
- Pycharm如何取消自动换行
1.只对当前文件有效的操作是: 菜单栏->View -> Active Editor -> Use Soft Wraps (不选中) 2.要是想对所有文件都起到效果,就要在setti ...
- OracleUNDO
UNDO作用 数据的回滚 一致性读 表的闪回(事务,查询的闪回....) 失败会话的恢复 数据的回滚 SQL> rollback; 回滚的过程就是从回滚段里拿到刚刚执行的这条语句产生的回滚,然后 ...
- Python菜鸟之路:Django 路由补充1:FBV和CBV - 补充2:url默认参数
一.FBV和CBV 在Python菜鸟之路:Django 路由.模板.Model(ORM)一节中,已经介绍了几种路由的写法及对应关系,那种写法可以称之为FBV: function base view ...
- 第09章—使用Lombok插件
spring boot 系列学习记录:http://www.cnblogs.com/jinxiaohang/p/8111057.html 码云源码地址:https://gitee.com/jinxia ...
- SpringMVC 之 RESTful 风格的增删改查
1. 视图和视图解析器 视图解析器 请求处理方法执行完成后,最终返回一个ModelAndView对象,对于返回String,View 或 ModelMap 等类型的处理方法, SpringMVC 也会 ...
- window下安装mysql详细步骤
1.下载安装包 打开mysql官网下载页面:http://dev.mysql.com/downloads/mysql/ 1.选择相应的版本和平台 2.mysql配置 打开刚刚解压的文件夹F:\mysq ...