action
核心概念
action 可以看作不收集依赖的 batch。它同样会把一组写操作合并到一次事务里,但会阻止函数内部的读取参与依赖追踪,因此更适合承载命令式更新逻辑。
描述
定义一个批量动作。与 batch 的唯一差别就是 action 内部是无法收集依赖的
与 batch 的关系
交互对比
<script setup lang="ts">
import { action, autorun, observable } from '@formily/reactive'
import { onBeforeUnmount, ref } from 'vue'
import { pushLog } from '../observable/shared'
const obs = observable({
aa: 0,
bb: 0,
})
const runCount = ref(0)
const rawAa = ref(obs.aa)
const rawBb = ref(obs.bb)
const logs = ref<string[]>([])
const handler = action.bound(() => {
obs.aa += 1
obs.bb += 1
})
const dispose = autorun(() => {
runCount.value += 1
rawAa.value = obs.aa
rawBb.value = obs.bb
pushLog(logs, `autorun #${runCount.value}: aa = ${obs.aa}, bb = ${obs.bb}`)
})
function runDirect() {
obs.aa += 1
obs.bb += 1
rawAa.value = obs.aa
rawBb.value = obs.bb
}
function runAction() {
handler()
rawAa.value = obs.aa
rawBb.value = obs.bb
}
function reset() {
obs.aa = 0
obs.bb = 0
rawAa.value = 0
rawBb.value = 0
runCount.value = 0
logs.value = []
}
onBeforeUnmount(() => dispose())
</script>
<template>
<div class="playground">
<p class="hint">
对比直接写入和 <code>action.bound</code>:两者都会更新数据,但
<code>action</code> 会把多次写入合并成一次事务。
</p>
<div class="toolbar">
<button class="btn" @click="runDirect">
direct writes
</button>
<button class="btn" @click="runAction">
action.bound()
</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">
autorun 次数
</div>
<div class="value">
{{ runCount }}
</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> 对比直接写入和 action.bound:两者都会更新数据,但 action 会把多次写入合并成一次事务。
aa / bb
0 / 0
autorun 次数
1
运行日志
- autorun #1: aa = 0, bb = 0
查看源码
示例代码
如果你希望把更新逻辑封装成可复用方法,可以用 action.bound 做高阶包装,得到和 batch 类似的合并更新效果:
ts
import { action, autorun, observable } from '@formily/reactive'
const obs = observable({})
const handler = action.bound(() => {
obs.aa = 123
obs.bb = 321
})
autorun(() => {
console.log(obs.aa, obs.bb)
})
handler()最终只会额外触发一次响应,即便 handler 内部有更多写操作,执行次数也不会继续线性增长。
签名
ts
interface action {
<T>(callback?: () => T): T // 原地action
scope: <T>(callback?: () => T) => T // 原地局部action
bound: <T extends (...args: any[]) => any>(callback: T, context?: any) => T // 高阶绑定
}用例
<script setup lang="ts">
import { action, autorun, observable } from '@formily/reactive'
import { onBeforeUnmount, ref } from 'vue'
import { pushLog } from '../observable/shared'
const obs = observable({
count: 0,
})
const countValue = ref(obs.count)
const runCount = ref(0)
const logs = ref<string[]>([])
const increase = action.bound(() => {
obs.count += 1
})
const dispose = autorun(() => {
runCount.value += 1
countValue.value = obs.count
pushLog(logs, `autorun #${runCount.value}: count = ${obs.count}`)
})
function increment() {
increase()
}
function reset() {
obs.count = 0
countValue.value = 0
runCount.value = 0
logs.value = []
}
onBeforeUnmount(() => dispose())
</script>
<template>
<div class="playground">
<p class="hint">
<code>action.bound</code> 适合把命令式更新逻辑封装成可复用方法。这里点击一次
<code>increase()</code>,只会产生一次额外响应。
</p>
<div class="toolbar">
<button class="btn" @click="increment">
increase()
</button>
<button class="btn secondary" @click="reset">
reset display
</button>
</div>
<div class="metrics">
<div class="metric">
<div class="label">
count
</div>
<div class="value">
{{ countValue }}
</div>
</div>
<div class="metric">
<div class="label">
autorun 次数
</div>
<div class="value">
{{ runCount }}
</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>action.bound 适合把命令式更新逻辑封装成可复用方法。这里点击一次 increase(),只会产生一次额外响应。
count
0
autorun 次数
1
运行日志
- autorun #1: count = 0
查看源码
示例代码
ts
import { action, observable } from '@formily/reactive'
const obs = observable({
count: 0,
})
const increase = action.bound(() => {
obs.count++
})
increase()