reaction
核心概念
reaction 是 observable 的订阅者。它接收一个 tracker 函数,在执行时如果读取了 observable 属性,就会与这些属性建立依赖关系;等到这些属性在别处发生写操作时,tracker 会再次执行。
和 autorun 不同,reaction 还会对 tracker 的返回值做一次脏检查,只有结果真正变化时才调用 subscriber,因此更适合精确控制副作用。

每次执行都会重新收集依赖,所以和 autorun 一样,在不再需要时也要主动 dispose。
描述
接收一个 tracker 函数,与 callback 响应函数,如果 tracker 内部有消费 observable 数据,数据发生变化时,tracker 函数会重复执行,但是 callback 执行必须要求 tracker 函数返回值发生变化时才执行
签名
ts
interface IReactionOptions<T> {
name?: string
equals?: (oldValue: T, newValue: T) => boolean // 脏检查
fireImmediately?: boolean // 是否第一次默认触发,绕过脏检查
}
interface reaction<T> {
(
tracker: () => T,
subscriber?: (newValue: T, oldValue: T) => void,
options?: IReactionOptions<T>
): void
}用例
<script setup lang="ts">
import { batch, observable, reaction } from '@formily/reactive'
import { onBeforeUnmount, ref } from 'vue'
import { pushLog } from '../observable/shared'
const obs = observable({
aa: 1,
bb: 2,
})
const rawAa = ref(obs.aa)
const rawBb = ref(obs.bb)
const trackerValue = ref(obs.aa + obs.bb)
const subscriberValue = ref<number | null>(null)
const trackerRuns = ref(0)
const subscriberRuns = ref(0)
const logs = ref<string[]>([])
const dispose = reaction(() => {
trackerRuns.value += 1
const next = obs.aa + obs.bb
trackerValue.value = next
pushLog(logs, `tracker #${trackerRuns.value}: sum = ${next}`)
return next
}, (next, prev) => {
subscriberRuns.value += 1
subscriberValue.value = next
pushLog(logs, `subscriber #${subscriberRuns.value}: ${prev} -> ${next}`)
})
function syncRawState() {
rawAa.value = obs.aa
rawBb.value = obs.bb
}
function swapWithBatch() {
batch(() => {
obs.aa = 2
obs.bb = 1
})
syncRawState()
}
function setAaToFour() {
obs.aa = 4
syncRawState()
}
function reset() {
obs.aa = 1
obs.bb = 2
rawAa.value = 1
rawBb.value = 2
trackerValue.value = 3
subscriberValue.value = null
trackerRuns.value = 0
subscriberRuns.value = 0
logs.value = []
}
syncRawState()
onBeforeUnmount(() => dispose())
</script>
<template>
<div class="playground">
<p class="hint">
<code>reaction</code> 会先执行 tracker,但只有 tracker 的返回值真的变化时才会调用
subscriber。批量交换 <code>aa</code> / <code>bb</code> 后,总和不变,所以 subscriber
不会执行。
</p>
<div class="toolbar">
<button class="btn" @click="swapWithBatch">
batch swap (2 / 1)
</button>
<button class="btn" @click="setAaToFour">
obs.aa = 4
</button>
<button class="btn secondary" @click="reset">
reset display
</button>
</div>
<div class="metrics">
<div class="metric">
<div class="label">
aa / bb
</div>
<div class="value">
{{ rawAa }} / {{ rawBb }}
</div>
</div>
<div class="metric">
<div class="label">
tracker 结果
</div>
<div class="value">
{{ trackerValue }}
</div>
</div>
<div class="metric">
<div class="label">
subscriber 结果
</div>
<div class="value">
{{ subscriberValue ?? '-' }}
</div>
</div>
<div class="metric">
<div class="label">
tracker / subscriber
</div>
<div class="value">
{{ trackerRuns }} / {{ subscriberRuns }}
</div>
</div>
</div>
<div>
<div class="sectionTitle">
运行日志
</div>
<ul class="logs">
<li v-for="(log, index) in logs" :key="`${index}-${log}`">
{{ log }}
</li>
</ul>
</div>
</div>
</template>
<style scoped src="../observable/shared.css"></style>reaction 会先执行 tracker,但只有 tracker 的返回值真的变化时才会调用 subscriber。批量交换 aa / bb 后,总和不变,所以 subscriber 不会执行。
aa / bb
1 / 2
tracker 结果
3
subscriber 结果
-
tracker / subscriber
1 / 0
运行日志
- tracker #1: sum = 3
查看源码
示例代码
ts
import { batch, observable, reaction } from '@formily/reactive'
const obs = observable({
aa: 1,
bb: 2,
})
const dispose = reaction(() => {
return obs.aa + obs.bb
}, console.log)
batch(() => {
// 不会触发,因为obs.aa + obs.bb值没变
obs.aa = 2
obs.bb = 1
})
obs.aa = 4
dispose()