React setState是异步吗?

news/2024/11/15 13:09:36/文章来源:https://www.cnblogs.com/wpshan/p/18547762
 

React官网对于setState的说明:

将setState()认为是一次请求而不是一次立即执行更新组件的命令。为了更为可观的性能,React可能会推迟它,稍后会一次性更新这些组件。React不会保证在setState之后,能够立刻拿到改变的结果。

以上说明执行setState时,有可能是异步(大部分情况下)更新组件(包括重新render ui以及及时修改组件this.state)。React为什么要做成大部分setState是异步的呢?有哪些情况是进行同步更新组件并且更新this.state的呢?

先说答案:在组件生命周期或React合成事件中,setState是异步;在setTimeout或者原生dom事件中,setState是同步。

为什么react大部分情况setState是异步的呢?假如所有setState是同步的,意味着每执行一次setState时(有可能一个同步代码中,多次setState),都重新vnode diff + dom修改,这对性能来说是极为不好的。如果是异步,则可以把一个同步代码中的多个setState合并成一次组件更新。

举个例子:

var Counter = React.createClass({getInitialState: function () {return { count: 0 };},handleClick: function () {// 同步代码中,多次setState最终只会执行一次组件更新(组件更新意味着this.state拿到最新值)this.setState({count: 1, }, (state) => {this.setState({ count : 3})console.log(this.state, 'next update') // 2
          });this.setState({ count: 2 });console.log(this.state, 'first') // 0 这就是大家常说的setState是异步过程,因为执行后组件state(this.state)没有改变// 同步表现setTimeout(() => {this.setState({count: 4})console.log(this.state, 'setTimeout') // 4 在setTimeout中执行setState,同步渲染ui以及及时更新this.state(同步表现)}, 0)},render: function () {console.log(this.state, 'render') // 2return (<button onClick={this.handleClick}>Click me! Number of clicks: {this.state.count}</button>
          );}});ReactDOM.render(<Counter />,document.getElementById('container'));
// 初始化时
{count: 0} "render"
// 单击按钮后
embedded:16 {count: 0} "first"
embedded:25 {count: 2} "render"
embedded:13 {count: 2} "next update"
embedded:25 {count: 3} "render"
embedded:25 {count: 4} "render"
embedded:20 {count: 4} "setTimeout"

react setState是如何实现的呢?异步更新的原理是什么呢?(以下源码分析基于react15.6)

setState异步实现

ReactComponent.prototype.setState = function(partialState, callback) {this.updater.enqueueSetState(this, partialState);if (callback) {this.updater.enqueueCallback(this, callback, 'setState');}
};enqueueSetState: function(publicInstance, partialState) {// 找到需渲染组件var internalInstance = getInternalInstanceReadyForUpdate(publicInstance,'setState',);if (!internalInstance) {return;}// 每次都把新的state,push到队列中。// 方便后面一次性更新组件时,聚合成最新的statevar queue =internalInstance._pendingStateQueue ||(internalInstance._pendingStateQueue = []);queue.push(partialState);// 更新
    enqueueUpdate(internalInstance);},
//代码位于ReactUpdateQueue.js
function enqueueUpdate(internalInstance) {ReactUpdates.enqueueUpdate(internalInstance);
}
//代码位于ReactUpdates.js
function enqueueUpdate(component) {ensureInjected();// 未开启事务流程:开启事务 + 更新组件// 在生命周期以及合成事件情况下,isBatchingUpdates=true// 在setTimeout以及原生DOM事件情况下,isBatchingUpdates=falseif (!batchingStrategy.isBatchingUpdates) {batchingStrategy.batchedUpdates(enqueueUpdate, component);return;}// 已开启事务流程:放到脏数组中(组件不更新 + this.state不变),等待更新
  dirtyComponents.push(component);if (component._updateBatchNumber == null) {component._updateBatchNumber = updateBatchNumber + 1;}
}

以上是setState的关键代码,batchingStrategy.batchedUpdates里面用到了事务机制。 setState 本身的方法调用是同步的,但是调用了setState不标志这react的 state 立即更新,这个更新是要根据当前环境执行上下文来判断的,如果处于batchedUpadte的情况下,那么state的不是当前立马更新的,而不处于batchedUpadte的情况下,那么他就有可能立马更新的。

所以在componentDidMount中调用setState并不会立即更新state,因为正处于一个更新流程中,isBatchingUpdates为true,所以只会放入dirtyComponents中等待稍后更新。

合成事件中调用setState

dispatchEvent: function (topLevelType, nativeEvent) {// disable了则直接不回调相关方法if (!ReactEventListener._enabled) {return;}var bookKeeping = TopLevelCallbackBookKeeping.getPooled(topLevelType, nativeEvent);try {// 在执行合成事件回调函数前,都先开启事务// 这样在执行回调函数里的setState时,都是放入脏数组时,往后更新
      ReactUpdates.batchedUpdates(handleTopLevelImpl, bookKeeping);} finally {TopLevelCallbackBookKeeping.release(bookKeeping);}
}ReactUpdates.batchedUpdates(callback, a, b, c, d, e) {ensureInjected();// 执行batchingStrategy.batchedUpdates意味着已开启事务流return batchingStrategy.batchedUpdates(callback, a, b, c, d, e);
}

更新组件处理state

更新组件时,有对state进行处理

ReactCompositeComponent.updateComponent: function(transaction,prevParentElement, // pre VNodenextParentElement, // next VNode
    prevUnmaskedContext,nextUnmaskedContext,) {var inst = this._instance;var prevProps = prevParentElement.props;var nextProps = nextParentElement.props;// componentWillReceiveProps 生命周期if (willReceive && inst.componentWillReceiveProps) {inst.componentWillReceiveProps(nextProps, nextContext);}// 对在pending队列中的state,进行merge state,拿到最新state值。var nextState = this._processPendingState(nextProps, nextContext);var shouldUpdate = true; // 是否要更新组件,默认是trueif (inst.shouldComponentUpdate) {// 如果组件里有定义shouldUpdate = inst.shouldComponentUpdate(nextProps,nextState,nextContext,);} else {// 如果是纯组件(PureComponent),浅比较if (this._compositeType === CompositeTypes.PureClass) {shouldUpdate =!shallowEqual(prevProps, nextProps) ||!shallowEqual(inst.state, nextState);}}// 是否更新组件,这里常是用户优化的地方,控制什么时候React组件什么时候更新。// 不设置就是true,子组件都会VDOM比较一遍(意味着子组件没变时,也会去比较(多余的操作,所以可以在此优化性能),不过浪费的性能是VDOM比较,而不是会改动DOM)。if (shouldUpdate) {// _performComponentUpdate --> _updateRenderedComponentthis._performComponentUpdate(nextParentElement,nextProps,nextState,nextContext,transaction,nextUnmaskedContext,);}},
// 合并state,拿到最新值
_processPendingState: function(props, context) {var inst = this._instance;var queue = this._pendingStateQueue;var replace = this._pendingReplaceState;this._pendingReplaceState = false;this._pendingStateQueue = null;// pending队列中只有0个或1个处理if (!queue) {return inst.state;}if (replace && queue.length === 1) {return queue[0];}// 多个处理var nextState = Object.assign({}, replace ? queue[0] : inst.state);for (var i = replace ? 1 : 0; i < queue.length; i++) {var partial = queue[i];Object.assign(nextState,typeof partial === 'function'? partial.call(inst, nextState, props, context): partial,);}return nextState;},
总结

如果在以下情况下执行setState方法:

  1. 生命周期里-此时已经开启了事务,当执行多个state时,所有都是在脏数组中,没有同步更新组件,意味着此时组件上的state没有更新。这也是为什么上面打印this.state.count会是0
  2. 合成事件回调函数里-下发事件时开启了事务,回调函数里执行setState都是放在脏数组中,同上
  3. setTimeout和DOM原生事件里,此时没有开启事务,直接同步更新组件 + 修改为最新的this.state

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/833988.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[LeetCode 最大岛屿面积

### DFS解法``` python class Solution:dir = [(-1,0),(1,0),(0,-1),(0,1)]def dfs(self,grid,x,DFS解法 class Solution:dir = [(-1,0),(1,0),(0,-1),(0,1)]def dfs(self,grid,x,y):if x < 0 or x >= len(grid) or y < 0 or y >= len(grid[0]) or grid[x][y] != 1…

【Ubuntu】在Ubuntu上安装微信

【Ubuntu】在Ubuntu上安装微信 零、说明 微信官网最近发布了Linux的测试版本,实际使用下来与Windows版本相差不多,本文介绍如何在Ubuntu(Debian系)上安装Linux版本的微信。 壹、下载 打开Linux微信官网:https://linux.weixin.qq.com/,根据自己的处理器架构选择对应的deb格…

文件共享服务之NFS挂载实验

任务需求 1.部署一台web服务器,提供静态网页的展示,该网站的html等静态资源远程存储在NFS服务器。 2.部署NFS服务器,创建共享文件夹(提供静态文件),发布该共享目录,提供给web服务器使用。 主机列表 # 外网地址 内网地址 主机名 192.168.122.207…

第 5 篇 Scrum 冲刺博客

团队作业4——第 5 篇 Scrum 冲刺博客 作业要求这个作业属于哪个课程 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13234这个作业的目标 团队集体协作完成项目开发队名 雄狮般的男人站立式…

Sitecore debug 工具

由于 Sitecore 的调试需要老是通过 log 来分析,而每次更新 dll 都需要等待一定的时间和重复执行前端的操作逻辑,特开发一个在线编辑器的方式来方便调试。警告:请勿在生产环境使用。截图构建项目 打开 Frontend,使用 pnpm i 安装依赖包,然后 pnpm build:vite 构建项目,之后…

如何禁止 SQL Server 中的 xp_cmdshell 以提高安全性

概述 在 SQL Server 中,xp_cmdshell 是一个强大的功能,它允许执行操作系统级别的命令。然而,这也带来了潜在的安全风险。本文将详细介绍如何禁止 xp_cmdshell,以增强 SQL Server 的安全性。 禁止 xp_cmdshell 的步骤 步骤 1:检查 xp_cmdshell 的当前状态 在开始禁止 xp_cm…

“亦”真“亦”假?——MXsteerWheel与DYNA4的强强联手

高性能力反馈方向盘MXsteerWheel作为线控转向开发的新帮手,在北汇信息的展台上一直是大家关注的焦点。由于它简练出众的外表与真实阻尼的手感,吸引参展的朋友都乐此不疲地进行尝试。而后又不禁感叹,亦真亦假!图1 桌面式驾驶模拟器 一、系统组成整体来看,驾驶模拟器由CANo…

c++_primer之第四章

4.1 节练习 练习 4.1 在算术运算符中,乘法和除法的优先级相同,且均高于加减法的优先级。因此 上式的计算结果应该是 105,在编程环境中很容易验证这一点。 练习 4.2 在本题涉及的运算符中,优先级最高的是成员选择运算符和函数调用运算符, 其次是解引用运算符,最后是加法运…

Docker Kubernetes

Docker镜像与容器 Docker 中有两个重要概念。 一个是容器(Container):容器特别像一个虚拟机,容器中运行着一个完整的操作系统。可以在容器中装 Nodejs,可以执行npm install,可以做一切你当前操作系统能做的事情 另一个是镜像(Image):镜像是一个文件,它是用来创建容器…

thinkphp升级后报错Declaration of think\app\Url::build() must be compatible with think\route\Url::build():

​ 将源码中的thinkphp升级后,发现了错误:Declaration of think\app\Url::build() must be compatible with think\route\Url::build(): string 出现这个错误的原因是,你通过命令“composer update topthink/framework”只升级了框架,没有更新多应用扩展模块。 只需要compo…

为什么 PHP 在 2024 年会越来越受欢迎:经典语言的意外回归

2024 年,PHP 出人意料地卷土重来,这得益于重大的性能改进、现代功能和蓬勃发展的生态系统。 在 Laravel 等框架的引领和广泛的托管支持下,PHP 已成为强大、快速且可靠的 Web 开发选择,这些证明它远未过时。 二十多年来,PHP 一直是 Web 开发的基石,为数百万个网站和 Web 应…

linux终端美化 oh-my-bash安装

之前一直在用zsh终端,然后安装oh-my-zsh,配置后感觉特别清爽,于是想bash终端下是否存在类似的软件,找了下发现了oh-my-bash。特记录下安装使用过程 oh my bash官网:https://ohmybash.nntoan.com/ oh my bash github:https://github.com/ohmybash/oh-my-bash系统终端SHELL…