为什么React.setState是异步的?
(1)保证内部的一致性
目前 React 提供的 objects 有三种:① state
② props
③ refs
这三个 object 的行为和表现都是一致的,它们能够保证构建出协调的树(reconciled tree
),这非常重要。
- 举个例子:假设
state
是同步更新的
class Child extends React.Component{
state={value:0,}
this.setState({ value: this.state.value + 1 }) // 1
this.setState({ value: this.state.value + 1 }) // 2
}
如果父组件也需要该 state
的 value
属性的话,就需要 状态提升:
父:
class Father extends React.Component{
state={value:0,}
Add(){
this.setState({ value: this.state.value + 1 })
}
render(){
return (<Child add={this.Add.bind(this)}/>)
}
}
子:
class Child extends React.Component{
this.props.add()
}
注意:虽然在此假设中 state
是同步的,会立即更新,但是 props
却不会立即更新,而是 0
this.props.onIncrement() // 0
this.props.onIncrement() // 0
为啥?
因为 在没有重新渲染 父组件 的情况下,React 不会立即更新 props
,如果 将 props
也设为同步更新的话,那么就必须放弃 批处理(batching
)。
state
、props
变化一次,就重渲染一次,这会显著地降低 React
性能。
- 还有个例子:
this.setState({this.state.value+this.props.value})
在此假设中 state
是立即更新的,但 props
不是,这就会导致 新的 state
的值不是期待的值。
综上:React 采用了 state
、props
采用 异步更新 的策略,保证内部一致性、保证状态提升更加安全。
(2)能够使用并发更新,从而优化性能
我们可能认为 状态更新是按照一定顺序更新的,但事实上,React
会根据不同的调用源,给不同的 setState()
分配不同的优先级。(妈的,我在上一篇文章还说 React
是顺序更新 state
的。。)
调用源:事件处理、网络请求、动画 等。
- 举个例子:
用户正在一个聊天室和其他用户聊天,他向 input 框输入文字的时候,其他用户给他发送了一个 message。
如果 state 是同步更新的,那是不是要立即重渲染 聊天室?
肯定不行,这会严重影响用户体验。
React 的做法是,将 message 的setState
分配成低优先级,延迟 几毫秒去刷新。
(只要我更新 message 的速度足够快,重渲染就跟不上我。。但不能保证任何时候都更新得足够快。)
React可能在 v17 的时候推出异步渲染 UI,这受益于 state
、props
的异步更新。
异步渲染 UI 有什么用?
- 举个例子:
当用户从 页面A 跳转到 页面B 的时候,通常会有一个loading
页面的效果。
但是,如果网络请求非常快的话,loading
效果会闪烁一下,再呈现 页面B,这会影响用户体验。
如果 React 有了 异步渲染 UI 的功能,那么当你 setState()
的时候,React 会“偷偷地”渲染 页面B:
如果 渲染 页面B 花费的时间较长,那么开发者可以 加一个 loading 效果;
如果 渲染 页面B 花费的时间hin短,那么 React
偷偷渲染好 页面B 后,可以无缝切换到页面B。
同时,异步渲染 还能让用户在等待的过程中,继续和 旧页面 交互。
综上:如果 同步更新 state
的话,就无法在幕后渲染新页面,同时还保持旧页面的交互。
参考:https://github.com/facebook/react/issues/11527#issuecomment-360199710
(完)