前言
@H_
404_2@不知道大家有没有过这个疑问,React 中
setState()
为什么是异步的?我一度认为
setState()
是同步的,知道它是异步的之后很是困惑,甚至期待 React 能出一个
setStateSync()
之类的 API。同样有此疑问的还有 MobX 的作者
Michel Weststrate,他认为经常听到的答案都很容易反驳,并认为这可能是一个历史包袱,所以开了一个
issue 询问真正的原因。最终这个 issue 得到了 React 核心成员
Dan Abramov 的
回复,Dan 的
回复表明这不是一个历史包袱,而是一个经过深思熟虑的设计。
@H_
404_2@注意:这篇
文章根据 Dan 的
回复写成,但不是一篇翻译。我忽略了很多不太重要的
内容,Dan 的完整
回复请看
这里。
正文
@H_
404_2@Dan 在
回复中表示为什么
setState()
是异步的,这并没有一个明显的答案(obv
IoUs answer),每种方案都有它的权衡。但是 React 的设计有以下几点考量:
一、保证内部的一致性
@H_
404_2@首先,我想我们都同意推迟并批量处理重渲染是有益而且对
性能优化很重要的,无论
setState()
是同步的还是异步的。那么就算让
state
同步更新,
props
也不行,因为当父组件重渲染(re-render )了你才知道
props
。
@H_
404_2@现在的设计保证了 React 提供的 objects(state,props,refs)的行为和表现都是一致的。为什么这很重要?Dan 举了个栗子:
@H_
404_2@假设
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
@H_
404_2@然而,这时你需要将状态提升到父组件,以供多个兄弟组件共享:
-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // 在父组件中做同样的事
@H_
404_2@需要指出的是,在 React 应用中这是一个很常见的重构,几乎每天都会发生。
@H_
404_2@然而下面的
代码却不能按预期工作:
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
@H_
404_2@这是因为同步模型中,虽然
this.state
会立即更新,但是
this.props
并不会。而且在没有重渲染父组件的情况下,我们不能立即更新
this.props
。如果要立即更新
this.props
(也就是立即重渲染父组件),就必须放弃批处理(根据情况的不同,
性能可能会有显著的下降)。
@H_
404_2@所以为了
解决这样的问题,在 React 中
this.state
和
this.props
都是异步更新的,在上面的例子中重构前跟重构后都会打印出 0。这会让状态提升更安全。
@H_
404_2@最后 Dan 总结说,React 模型更愿意保证内部的一致性和状态提升的安全性,而不总是追求
代码的简洁性。
@H_
404_2@我们通常认为状态更新会按照既定顺序被应用,无论
state
是同步更新还是异步更新。然而事实并不一定如此。
@H_
404_2@React 会依据不同的
调用源,给不同的
setState()
调用分配不同的优先级。
调用源
包括事件处理、网络请求、动画等。
@H_
404_2@Dan 又举了个栗子。假设你在一个聊天窗口,你正在输入消息,
TextBox
组件中的
setState()
调用需要被立即应用。然而,在你输入过程中又收到了一条新消息。更好的处理方式或许是延迟渲染新的
MessageBubble
组件,从而让你的输入更加顺畅,而不是立即渲染新的
MessageBubble
组件阻塞线程,导致你输入抖动和延迟。
@H_
404_2@如果给某些更新分配低优先级,那么就可以把它们的渲染分拆为几个毫秒的块,
用户也不会注意到。
三、更多的可能性
@H_
404_2@Dan 最后说到,异步更新并不只关于
性能优化,而是 React 组件模型能做什么的一个根本性转变(fundamental shift)。
@H_
404_2@Dan 还是举了个栗子。假设你从一个
页面导航到到另一个
页面,通常你需要展示一个加载动画,等待新
页面的渲染。但是如果导航非常快,闪烁一下加载动画又会降低
用户体验。
@H_
404_2@如果这样会不会好点,你只需要简单的
调用 setState()
去渲染一个新的
页面,React “在幕后”开始渲染这个新的
页面。想象一下,不需要你写任何的协调
代码,如果这个更新花了比较长的时间,你可以展示一个加载动画,否则在新
页面准备好后,让 React 执行一个无缝的切换。此外,在等待过程中,旧的
页面依然可以交互,但是如果花费的时间比较长,你必须展示一个加载动画。
@H_
404_2@事实证明,在现在的 React 模型基础上做一些
生命周期调整,真的可以实现这种设想。
@acdlite 已经为这个
功能努力几周了,并且很快会发布一个
RFC(亦可赛艇!)。
@H_
404_2@需要注意的是,异步更新
state
是有可能实现这种设想的前提。如果同步更新
state
就没有办法在幕后渲染新的
页面,还保持旧的
页面可以交互。它们之间独立的状态更新会冲突。
@H_
404_2@Dan 最后对 Michel 说到:我希望我们能在接下来几个月说服你,并且你会欣赏到 React 模型的灵活性。据我理解,这种灵活性至少一部分要归功于
state
的异步更新。
原文链接:https://www.f2er.com/react/301989.html