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.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详解相关推荐
- react中的setState详解
1.setState更新状态的2种写法 (1). setState(stateChange, [callback])------对象式的setState 1.stateChange为状态改变对象( ...
- react setState详解
React setState调用的原理 setState具体的执行过程如下: 首先调用setState()函数: ReactComponent.prototype.setState = functio ...
- Rocksdb 写流程,读流程,WAL文件,MANIFEST文件,ColumnFamily,Memtable,SST文件原理详解
文章目录 前言 Rocksdb写流程图 WAL 原理分析 概述 文件格式 查看WAL的工具 创建WAL 清理WAL MANIFEST原理分析 概述 查看MANIFEST的工具 创建 及 清除 MANI ...
- ReactNative ViewPageAndroid组件详解
源码传送门 在我们开发Android的时候,ViewPage这个控件的使用频率还是很高的,最简单的就是制作引导页,应用程序的主界面等,在ReactNative开发中实现该功能的组件是ViewPageA ...
- 解析 http 请求 header 错误_详解http报文(2)-web容器是如何解析http报文的
摘要 在详解http报文一文中,详细介绍了http报文的文本结构.那么作为服务端,web容器是如何解析http报文的呢?本文以jetty和undertow容器为例,来解析web容器是如何处理http报 ...
- aqs java_Java并发之AQS详解
一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS)! 类如其名,抽象的队列式的同步器,AQ ...
- 你真的弄明白了吗?Java并发之AQS详解
你真的弄明白了吗?Java并发之AQS详解 带着问题阅读 1.什么是AQS,它有什么作用,核心思想是什么 2.AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁 3.AQS在J ...
- java lock unlock_详解Java中的ReentrantLock锁
ReentrantLock锁 ReentrantLock是Java中常用的锁,属于乐观锁类型,多线程并发情况下.能保证共享数据安全性,线程间有序性 ReentrantLock通过原子操作和阻塞实现锁原 ...
- 2W 字详解设计模式!
- 前言 - 设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four (GoF) 的分类了,他们将设计模式分类为 23 种经典的模式,根据 ...
最新文章
- python代码找到链表的倒数第K个节点并打印
- php的框架目录,Laravel 框架目录结构
- BZOJ 4823 Luogu P3756 [CQOI2017]老C的方块 (网络流、最小割)
- 2019 湖南多校第五场题解
- Eclipse Juno在ubuntud的安装(SVN)
- 在Axapta中实现trim函数
- leetcode 516. Longest Palindromic Subsequence | 516. 最长回文子序列(递归 -> 傻缓存 ->DP)
- java设计模式之UML③类图讲解多个类之间的关系
- javax.servlet.jsp.PageContext cannot be resolve...
- Spring注解之@Import用法解析
- python中连续两个小于号_Python语言描述最大连续子序列和
- Struts 2的拦截器(Interceptor)总结
- 装饰模式 -- 大话设计模式
- Oracle 19c 安装步骤
- JspStudy环境下tomcat服务器无法正确运行servlet的一种解决方法
- “电容触摸按键实验”实例解析
- 【Linux】Ubuntu18.04安装微信和QQ【2022】
- 避免 Time Limit Exceeded
- Java 初级软件工程师 认证考试试卷1
- 关于第一型曲面积分的再思考
热门文章
- “好声音“连唱10年,星空华文如何唱响港交所?
- android dialogfragment 生命周期,DialogFragment生命周期简介
- 谷歌开源Gumbo:纯C语言实现的HTML5解析库
- GPU推理和端侧NPU推理的一处不同
- 关于ruby安装git(或别网站)的安装包的443port错误(用homebrew做事例)Failed to connect to raw.github.com port 443:
- slf4j 使用 java_[JAVA]SLF4J及其实现类框架使用简介
- JS 去除输入框里面空格、英文字符、换行符
- SQL数据库视图关联
- shell获取天气预报
- 大数据是阿猫阿狗都能玩的吗