React 中 setState() 为什么是异步的?

Stella981
• 阅读 354

React 中 setState() 为什么是异步的?

React 中 setState() 为什么是异步的?

不知道大家有没有过这个疑问,React 中 setState() 为什么是异步的?很多人一度认为 setState() 是同步的,知道它是异步的之后很是困惑,甚至期待 React 能出一个 setStateSync() 之类的 API。同样有此疑问的还有 MobX 的作者 Michel Weststrate,他认为经常听到的答案都很容易反驳,并认为这可能是一个历史包袱,所以开了一个 issue 询问真正的原因。最终这个 issue 得到了 React 核心成员 Dan Abramov 的回复,Dan 的回复表明这不是一个历史包袱,而是一个经过深思熟虑的设计。

React 中 setState() 为什么是异步的?

注意:这篇文章根据 Dan 的回复总结分析。

Dan 在回复中表示为什么 setState() 是异步的,这并没有一个明显的答案(obvious answer),每种方案都有它的权衡。但是 React 的设计有以下几点考量:

一、保证内部的一致性

首先,我想我们都同意推迟并批量处理重渲染是有益而且对性能优化很重要的,无论 setState() 是同步的还是异步的。那么就算让 state 同步更新,props 也不行,因为当父组件重渲染(re-render )了你才知道 props

现在的设计保证了 React 提供的 objects(state,props,refs)的行为和表现都是一致的。为什么这很重要?Dan 举了个栗子:

假设 state 是同步更新的,那么下面的代码是可以按预期工作的:

console.log(this.state.value) // 0this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

然而,这时你需要将状态提升到父组件,以供多个兄弟组件共享:

-this.setState({ value: this.state.value + 1 });+this.props.onIncrement(); // 在父组件中做同样的事

需要指出的是,在 React 应用中这是一个很常见的重构,几乎每天都会发生。

然而下面的代码却不能按预期工作:

console.log(this.props.value) // 0this.props.onIncrement();
console.log(this.props.value) // 0this.props.onIncrement();
console.log(this.props.value) // 0

这是因为同步模型中,虽然 this.state 会立即更新,但是 this.props 并不会。而且在没有重渲染父组件的情况下,我们不能立即更新 this.props。如果要立即更新 this.props (也就是立即重渲染父组件),就必须放弃批处理(根据情况的不同,性能可能会有显著的下降)。

所以为了解决这样的问题,在 React 中 this.statethis.props 都是异步更新的,在上面的例子中重构前跟重构后都会打印出 0。这会让状态提升更安全。

最后 Dan 总结说,React 模型更愿意保证内部的一致性和状态提升的安全性,而不总是追求代码的简洁性。

二、性能优化

我们通常认为状态更新会按照既定顺序被应用,无论 state 是同步更新还是异步更新。然而事实并不一定如此。

React 会依据不同的调用源,给不同的 setState() 调用分配不同的优先级。调用源包括事件处理、网络请求、动画等。

Dan 又举了个栗子。假设你在一个聊天窗口,你正在输入消息,TextBox 组件中的 setState() 调用需要被立即应用。然而,在你输入过程中又收到了一条新消息。更好的处理方式或许是延迟渲染新的 MessageBubble 组件,从而让你的输入更加顺畅,而不是立即渲染新的 MessageBubble 组件阻塞线程,导致你输入抖动和延迟。

如果给某些更新分配低优先级,那么就可以把它们的渲染分拆为几个毫秒的块,用户也不会注意到。

三、更多的可能性

Dan 最后说到,异步更新并不只关于性能优化,而是 React 组件模型能做什么的一个根本性转变(fundamental shift)。

Dan 还是举了个栗子。假设你从一个页面导航到到另一个页面,通常你需要展示一个加载动画,等待新页面的渲染。但是如果导航非常快,闪烁一下加载动画又会降低用户体验。

如果这样会不会好点,你只需要简单的调用 setState() 去渲染一个新的页面,React “在幕后”开始渲染这个新的页面。想象一下,不需要你写任何的协调代码,如果这个更新花了比较长的时间,你可以展示一个加载动画,否则在新页面准备好后,让 React 执行一个无缝的切换。此外,在等待过程中,旧的页面依然可以交互,但是如果花费的时间比较长,你必须展示一个加载动画。

事实证明,在现在的 React 模型基础上做一些生命周期调整,真的可以实现这种设想。@acdlite 已经为这个功能努力几周了,并且很快会发布一个 RFC(亦可赛艇!)。

需要注意的是,异步更新 state 是有可能实现这种设想的前提。如果同步更新 state 就没有办法在幕后渲染新的页面,还保持旧的页面可以交互。它们之间独立的状态更新会冲突。

Dan 最后对 Michel 说到:我希望我们能在接下来几个月说服你,并且你会欣赏到 React 模型的灵活性。

据我理解,这种灵活性至少一部分要归功于 state 的异步更新。


React 中 setState() 为什么是异步的?

本文分享自微信公众号 - 前端研究所(WEBqdyjs)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
6个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
KaliTools说明书+BurpSuit实战指南+SQL注入知识库+国外渗透报告
!(https://oscimg.oschina.net/oscnet/d1c876a571bb41a7942dd9752f68632e.gif"15254461546.gif")0X00KaliLinux Tools中文说明书!(https://oscimg.oschina.net/oscnet/
Wesley13 Wesley13
3年前
MySQL总结(十一)子查询
!(https://oscimg.oschina.net/oscnet/upa344f41e81d3568e3310b5da00c57ced8ea.png)子查询1\.什么是子查询需求:查询开发部中有哪些员工selectfromemp;通
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这