我们都知道,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详解的更多相关文章

  1. 深入React技术栈之setState详解

    抛出问题 class Example extends Component { contructor () { super() this.state = { value: 0, index: 0 } } ...

  2. Lock的实现之ReentrantLock详解

    摘要 Lock在硬件层面依赖CPU指令,完全由Java代码完成,底层利用LockSupport类和Unsafe类进行操作: 虽然锁有很多实现,但是都依赖AbstractQueuedSynchroniz ...

  3. [转] ReactNative Animated动画详解

    http://web.jobbole.com/84962/     首页 所有文章 JavaScript HTML5 CSS 基础技术 前端职场 工具资源 更多频道▼ - 导航条 - 首页 所有文章 ...

  4. AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  5. 【Java并发】详解 AbstractQueuedSynchronizer

    前言 队列同步器 AbstractQueuedSynchronizer(以下简称 AQS),是用来构建锁或者其他同步组件的基础框架.它使用一个 int 成员变量来表示同步状态,通过 CAS 操作对同步 ...

  6. 详解 Node + Redux + MongoDB 实现 Todolist

    前言 为什么要使用 Redux? 组件化的开发思想解放了繁琐低效的 DOM 操作,以 React 来说,一切皆为状态,通过状态可以控制视图的变化,然后随着应用项目的规模的不断扩大和应用功能的不断丰富, ...

  7. Java经典设计模式之十一种行为型模式(附实例和详解)

    Java经典设计模式共有21中,分为三大类:创建型模式(5种).结构型模式(7种)和行为型模式(11种). 本文主要讲行为型模式,创建型模式和结构型模式可以看博主的另外两篇文章:Java经典设计模式之 ...

  8. Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  9. Netty4.x整合SpringBoot2.x使用Protobuf3详解

    前言 本篇文章主要介绍的是SpringBoot整合Netty以及使用Protobuf进行数据传输的相关内容.Protobuf会介绍下用法,至于Netty在netty 之 telnet HelloWor ...

随机推荐

  1. 富文本编辑期Quill

    官方网站http://quilljs.com/ 使用方法 <!-- Create the toolbar container --> <div id="toolbar&qu ...

  2. Django admin 的 9 个技巧

    Tip 1:Django admin 后台不限于用 Django 开发的网站 虽然 Django admin 管理界面可以非常友好的用在 Django 项目的其它部分,它同样可以很容易用于其它像传统的 ...

  3. redis Could not connect to Redis at 127.0.0.1:6379: Connection refused 问题解决

    1.启动redis 客户端 redis-cli 报错 redis Could not connect to Redis at 127.0.0.1:6379: Connection refused 是因 ...

  4. Python实现生命游戏

    1. 生命游戏是什么 生命游戏是英国数学家约翰·何顿·康威在1970年发明的细胞自动机.它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死了的细胞.一个细胞在下一个时刻生死取决于相邻八个 ...

  5. 【BZOJ1996】[Hnoi2010]chorus 合唱队 区间DP

    [BZOJ1996][Hnoi2010]chorus 合唱队 Description Input Output Sample Input 4 1701 1702 1703 1704 Sample Ou ...

  6. js如何去除多个cookie?

    转自:https://zhidao.baidu.com/question/211006012.html :1:设置cookie 最简单的就是:document.cookie="user=aa ...

  7. zookeeper简单操作

    接下来主要讲述了通过zookeeper服务器自带的zkCli.sh工具模拟客户端访问和操作zookeeper服务器(包括集群服务器). 当成功启动zookeeper服务后,切换到server1/bin ...

  8. 改变label中的某字体颜色

    NSString *allString=@"你家在哪里,需要我送你么"; NSString *subString=@"在哪里"; UILabel *string ...

  9. Too Many Open Files的错误

    百度Elasticsearch-产品描述-介绍-百度云 https://cloud.baidu.com/doc/BES/FAQ.html#Too.20Many.20Open.20Files.E7.9A ...

  10. mysql多条更新

    最近在完成MySql项目集成的情况下,需要增加批量更新的功能,根据网上的资料整理了一下,很好用,都测试过,可以直接使用. mysql 批量更新共有以下四种办法 1..replace into 批量更新 ...