java setstate,深入剖析setState同步异步机制

关于 setState

setState 的更新是同步还是异步,一直是人们津津乐道的话题。不过,实际上如果我们需要用到更新后的状态值,并不需要强依赖其同步/异步更新机制。在类组件中,我们可以通过this.setState的第二参数、componentDidMount、componentDidUpdate等手段来取得更新后的值;而在函数式组件中,则可以通过useEffect来获取更新后的状态。所以这个问题,其实有点无聊。

不过,既然大家都这么乐于讨论,今天我们就系统地梳理一下这个问题,主要分为两方面来说:

类组件(class-component)的更新机制

函数式组件(function-component)的更新机制

类组件中的 this.setState

在类组件中,这个问题的答案是多样的,首先抛第一个结论:

在legacy模式中,更新可能为同步,也可能为异步;

在concurrent模式中,一定是异步。

问题一、legacy 模式和 concurrent 模式是什么鬼?

通过ReactDOM.render(, rootNode)方式创建应用,则为 legacy 模式,这也是create-react-app目前采用的默认模式;

通过ReactDOM.unstable_createRoot(rootNode).render()方式创建的应用,则为concurrent模式,这个模式目前只是一个实验阶段的产物,还不成熟。

legacy 模式下可能同步,也可能异步?

是的,这不是玄学,我们来先抛出结论,再来逐步解释它。

当直接调用时this.setState时,为异步更新;

当在异步函数的回调中调用this.setState,则为同步更新;

当放在自定义 DOM 事件的处理函数中时,也是同步更新。

实验代码如下:

class StateDemo extends React.Component { constructor(props) { super(props) this.state = { count: 0 } } render() { return

{this.state.count}

累加
} increase = () => { this.setState({ count: this.state.count + 1 }) // 异步的,拿不到最新值 console.log('count', this.state.count) // setTimeout 中 setState 是同步的 setTimeout(() => { this.setState({ count: this.state.count + 1 }) // 同步的,可以拿到 console.log('count in setTimeout', this.state.count) }, 0) } bodyClickHandler = () => { this.setState({ count: this.state.count + 1 }) // 可以取到最新值 console.log('count in body event', this.state.count) } componentDidMount() { // 自己定义的 DOM 事件,setState 是同步的 document.body.addEventListener('click', this.bodyClickHandler) } componentWillUnmount() { // 及时销毁自定义 DOM 事件 document.body.removeEventListener('click', this.bodyClickHandler) }}

要解答上述现象,就必须了解 setState 的主流程,以及 react 中的 batchUpdate 机制。

首先我们来看看 setState 的主流程:

调用this.setState(newState);

newState会存入 pending 队列;

3,判断是不是batchUpdate;

4,如果是batchUpdate,则将组件先保存在所谓的脏组件dirtyComponents中;如果不是batchUpdate,那么就遍历所有的脏组件,并更新它们。

由此我们可以判定:所谓的异步更新,都命中了batchUpdate,先保存在脏组件中就完事;而同步更新,总是会去更新所有的脏组件。

非常有意思,看来是否命中batchUpdate是关键。问题也随之而来了,为啥直接调用就能命中batchUpdate,而放在异步回调里或者自定义 DOM 事件中就命中不了呢?

这就涉及到一个很有意思的知识点:react 中函数的调用模式。对于刚刚的 increase 函数,还有一些我们看不到的东西,现在我们通过魔法让其显现出来:

increase = () => { // 开始:默认处于bashUpdate // isBatchingUpdates = true this.setState({ count: t.........

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值