资讯专栏INFORMATION COLUMN

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

anonymoussf / 391人阅读

摘要:正文在回复中表示为什么是异步的,这并没有一个明显的答案,每种方案都有它的权衡。需要注意的是,异步更新是有可能实现这种设想的前提。

前言

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

注意:这篇文章根据 Dan 的回复写成,但不是一篇翻译。我忽略了很多不太重要的内容,Dan 的完整回复请看这里。

正文

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

一、保证内部的一致性

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

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

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

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.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) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.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 的异步更新。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107054.html

相关文章

  • setState异步、同步与进阶

    摘要:根本原因在于,并不是真正意义上的异步操作,它只是模拟了异步的行为。而合成事件和生命周期函数中,是受控制的,其会将设置为,从而走的是类似异步的那一套。总结此处总结是直接引用了只在合成事件和钩子函数中是异步的,在原生事件和中都是同步的。 如何使用setState 在 React 日常的使用中,一个很重要的点就是,不要直接去修改 state。例如:this.state.count = 1是无...

    widuu 评论0 收藏0
  • React-setState杂记

    摘要:简单的举下例子如等生命周期以及的事件即为异步更新,这里不显示具体代码。因为只有当父组件后才传给子组件,那么如果要变成同步的,就需要放弃。 前言 在看React的官方文档的时候, 发现了这么一句话,State Updates May Be Asynchronous,于是查询了一波资料, 最后归纳成以下3个问题 setState为什么要异步更新,它是怎么做的? setState什么时候会...

    yuxue 评论0 收藏0
  • 关于ReactsetState

    摘要:而在第二个参数中我们输出了改变后的即第五行输出,表明我们的更改生效了。而在的回调内,我们还调用了一个定义于内的事件函数,但是该事件函数内的也是同步的形式。 在react中,setState是用以改变class组件状态的函数,它有两种用法:一 传入一个updater函数,该函数有两个参数,一个是当前的state,还有一个是当前的props。该函数的返回值需要是一个更改的state值的对象...

    qieangel2013 评论0 收藏0
  • ReactsetState异步

    摘要:在学习的过程中几乎所有学习材料都会反复强调一点是异步的来看一下官网对于的说明。将认为是一次请求而不是一次立即执行更新组件的命令。总结在组件生命周期中或者事件绑定中,是通过异步更新的。在延时的回调或者原生事件绑定的回调中调用不一定是异步的。 在学习react的过程中几乎所有学习材料都会反复强调一点setState是异步的,来看一下react官网对于setState的说明。 将setSta...

    hankkin 评论0 收藏0
  • React专题:可变状态

    摘要:的参数既可以是一个对象,也可以是一个回调函数。回调函数提供了两个参数,第一个参数就是计算过的对象,即便这时还没有渲染,得到的依然是符合直觉的计算过的值。专题一览什么是可变状态不可变属性生命周期组件事件操作抽象 本文是『horseshoe·React专题』系列文章之一,后续会有更多专题推出来我的 GitHub repo 阅读完整的专题文章来我的 个人博客 获得无与伦比的阅读体验 Reac...

    hosition 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<