我们都知道,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.statethis.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马上生效。

setState详解相关推荐

  1. react中的setState详解

    1.setState更新状态的2种写法 (1). setState(stateChange, [callback])------对象式的setState ​ 1.stateChange为状态改变对象( ...

  2. react setState详解

    React setState调用的原理 setState具体的执行过程如下: 首先调用setState()函数: ReactComponent.prototype.setState = functio ...

  3. Rocksdb 写流程,读流程,WAL文件,MANIFEST文件,ColumnFamily,Memtable,SST文件原理详解

    文章目录 前言 Rocksdb写流程图 WAL 原理分析 概述 文件格式 查看WAL的工具 创建WAL 清理WAL MANIFEST原理分析 概述 查看MANIFEST的工具 创建 及 清除 MANI ...

  4. ReactNative ViewPageAndroid组件详解

    源码传送门 在我们开发Android的时候,ViewPage这个控件的使用频率还是很高的,最简单的就是制作引导页,应用程序的主界面等,在ReactNative开发中实现该功能的组件是ViewPageA ...

  5. 解析 http 请求 header 错误_详解http报文(2)-web容器是如何解析http报文的

    摘要 在详解http报文一文中,详细介绍了http报文的文本结构.那么作为服务端,web容器是如何解析http报文的呢?本文以jetty和undertow容器为例,来解析web容器是如何处理http报 ...

  6. aqs java_Java并发之AQS详解

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

  7. 你真的弄明白了吗?Java并发之AQS详解

    你真的弄明白了吗?Java并发之AQS详解 带着问题阅读 1.什么是AQS,它有什么作用,核心思想是什么 2.AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁 3.AQS在J ...

  8. java lock unlock_详解Java中的ReentrantLock锁

    ReentrantLock锁 ReentrantLock是Java中常用的锁,属于乐观锁类型,多线程并发情况下.能保证共享数据安全性,线程间有序性 ReentrantLock通过原子操作和阻塞实现锁原 ...

  9. 2W 字详解设计模式!

    -     前言    - 设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four (GoF) 的分类了,他们将设计模式分类为 23 种经典的模式,根据 ...

最新文章

  1. python代码找到链表的倒数第K个节点并打印
  2. php的框架目录,Laravel 框架目录结构
  3. BZOJ 4823 Luogu P3756 [CQOI2017]老C的方块 (网络流、最小割)
  4. 2019 湖南多校第五场题解
  5. Eclipse Juno在ubuntud的安装(SVN)
  6. 在Axapta中实现trim函数
  7. leetcode 516. Longest Palindromic Subsequence | 516. 最长回文子序列(递归 -> 傻缓存 ->DP)
  8. java设计模式之UML③类图讲解多个类之间的关系
  9. javax.servlet.jsp.PageContext cannot be resolve...
  10. Spring注解之@Import用法解析
  11. python中连续两个小于号_Python语言描述最大连续子序列和
  12. Struts 2的拦截器(Interceptor)总结
  13. 装饰模式 -- 大话设计模式
  14. Oracle 19c 安装步骤
  15. JspStudy环境下tomcat服务器无法正确运行servlet的一种解决方法
  16. “电容触摸按键实验”实例解析
  17. 【Linux】Ubuntu18.04安装微信和QQ【2022】
  18. 避免 Time Limit Exceeded
  19. Java 初级软件工程师 认证考试试卷1
  20. 关于第一型曲面积分的再思考

热门文章

  1. “好声音“连唱10年,星空华文如何唱响港交所?
  2. android dialogfragment 生命周期,DialogFragment生命周期简介
  3. 谷歌开源Gumbo:纯C语言实现的HTML5解析库
  4. GPU推理和端侧NPU推理的一处不同
  5. 关于ruby安装git(或别网站)的安装包的443port错误(用homebrew做事例)Failed to connect to raw.github.com port 443:
  6. slf4j 使用 java_[JAVA]SLF4J及其实现类框架使用简介
  7. JS 去除输入框里面空格、英文字符、换行符
  8. SQL数据库视图关联
  9. shell获取天气预报
  10. 大数据是阿猫阿狗都能玩的吗