@formily/reactive 源码解读(4)

564 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

主流程03-batch

在这一部分,我们要捡起之前放在一边的 batch 。

batch 可以看作是一种表示“批量处理”的运行状态,涉及两个关键函数 batchStart 和 batchEnd ,从命名上看标志着 batch 过程的开始和结束。我们看看其代码实现。

// @formily/reactive/src/reaction

export const batchStart = () => {
  BatchCount.value++
}

export const batchEnd = () => {
  BatchCount.value--
  if (BatchCount.value === 0) {
    const prevUntrackCount = UntrackCount.value
    UntrackCount.value = 0
    executePendingReactions()
    executeBatchEndpoints()
    UntrackCount.value = prevUntrackCount
  }
}

可以这样理解,bacthStart 和 batchEnd 通过控制 BatchCount.value 来控制当前的运行状态。而这个状态是什么?这个状态下会做什么呢?

实际前文也提到过,这就是 isBatching 的状态,代码中的定义如下:

export const isBatching = () => BatchCont.value > 0

并且,isBatching 标志变量只在一个地方使用,那就是作为一个条件判断决定 reaction 是否立即执行(runReaction 函数中)。

如果 isBatching 为 true,那么将暂存 Reaction,当进入 batchEnd 后且 batchCount.value 为0时执行 Reaction;反之,则会立即执行 Reaction。

值得一提的这个“暂存”的过程,代码如下:

// 代码省略
else if (isBatching()) {
      PendingReactions.add(reaction)
}

PendingReactions 在文件中被定义为一个 ArraySet 类型,如下所示:

export const PendingReactions = new ArraySet<Reaction>()

而这个 ArraySet 在加入成员时,会有一个去重的操作。也就是说,在 isBatching 状态下,加入两个及其以上相同的 Reaction,只会在 PendingReactions 中保留一个,后续只会触发一次该 Reaction。

到这里,已经能够猜出设计 batch 就是为了批量处理。而一般来说,批量处理意味着节流、减少性能消耗。在 reactive 中,批量处理的是 Reaction 的执行。

和上一小节中有关 Reaction 绑定和触发的内容联系起来。这就要看,在什么情况下,会进入 isBatching 的状态,从而避免单个 Reaction 立即执行或者相同 Reaction 的多次触发。

  1. Reaction 执行

    batchStart 在 tracker 执行前调用,tracker 执行完毕后调用 batchEnd。这意味着 tracker 执行期间,也就是在对 observable 对象进行访问时,处于 isBatching 状态,此时,不能立即执行 Reaction。这是合理的,因为已经处于一个 Reaction 的调用栈中,不能调用其他的 Reaction。否则,Reaction 的绑定、对象属性的访问可能出现混乱。

  2. runReactionsFromTargetKey

    batchStart 和 batchEnd 分别在 runReactions 执行之前和之后调用。这意味着对于同一对象的同一属性所绑定的 Reaction 遍历执行阶段,处于 isBatching 状态,此时,不能立即执行 Reaction。

  3. 当用户调用 batch API 自定义批量处理过程时(这部分会在后面讲解该 API 时提到的)。