react div onclick叠加_深入 react 细节之 - batchUpdate

实验

作为 react 的用户,大家多多少少听说过 react 会有 batch update 的策略,那具体是怎样的呢?我们写个 demo:

  • 代码 https://codesandbox.io/s/48lp729k80
  • 预览地址:https://48lp729k80.codesandbox.io/

d19b539a55e3a824b90473abe870481e.gif

大家可以点开上面的预览地址试一下,这三个 button 的 handelr 内部都以不同形式执行了两次 setState 语句。

但是结果,

  • 第一个和第三个 button 都只触发了一次 render
  • 而第二个 button 触发了两次 render

我这里先抛出结论:

  • 在 react 的 event handler 内部同步的多次 setState 会被 batch 为一次更新
  • 在一个异步的事件循环里面多次 setState,react 不会 batch
  • 可以使用 ReactDOM.unstable_batchedUpdates 来强制 batch
import 

解释

https://github.com/facebook/react/issues/10231 这里有 Dan Abramov 对此的回答,大致来说就是虽然目前是这样,但是未来 React 希望做到不管里你在哪里写 setState,一个 tick 内的多次 setState 都给你合并掉。

为什么 react 要这么设计?

https://overreacted.io/react-as-a-ui-runtime/#batching 这里有非常好的解释,大致翻译一下

function Parent() {
  let [count, setCount] = useState(0);
  return (
    <div onClick={() => setCount(count + 1)}>
      Parent clicked {count} times
      <Child />
    </div>
  );
}

function Child() {
  let [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Child clicked {count} times
    </button>
  );
}

上面这样的 demo,由于点击事件冒泡的缘故,我们假设如果 react 不 batch 立即更新的话,那么点了 child button 之后的逻辑会是如下这样

*** 进入 react click 的事件函数 ***
Child (onClick) 触发点击
  - setState 修改 state
  - re-render Child 重新渲染 //   不必要的
Parent (onClick) 触发点击(冒泡)
  - setState 修改 state
  - re-render Parent 重新渲染
  - re-render Child 重新渲染 (渲染是自顶向下的,父亲更新会导致儿子更新)
*** 退出 react click 的事件函数  ***

从上面可以看出,第一次子组件的重新渲染完全是浪费的。

所以 React 设计成 setState 不立即触发重新渲染,而是先执行完所有的 event handler,然后用一次重新渲染完成所有更新。

为什么 setTimeout 定时器内的任务没法被 batch 呢?

可以先大致看一下 ReactDOM.unstable_batchedUpdates 的代码,可以看到有 isBatchingUpdates: boolean 这样一个 flag

  • https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js#L801
  • https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberScheduler.js#L2481

简单来解释,React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook (有点类似 koa 的 middleware),在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。

但是 setTimeout 这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。

为什么 Vue 没有这个限制呢?

是因为 vue 采用了 nexttick 的方式,利用 EventLoop,将一个同步事件循环过程中所有修改合并,它本质上属于延迟的批量更新

  • https://github.com/vuejs/vue/blob/dev/src/core/util/next-tick.js
  • https://cn.vuejs.org/v2/guide/reactivity.html#%E5%BC%82%E6%AD%A5%E6%9B%B4%E6%96%B0%E9%98%9F%E5%88%97

batchedUpdates 的应用场景

react-easy-state 就使用了 ReactDOM.unstable_batchedUpdates 来实现了框架层面的 batch 更新 API:https://github.com/solkimicreb/react-easy-state/blob/master/src/scheduler.js#L9

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值