autorun
核心概念
autorun 是最直接的 reaction 形式。它会立即执行 tracker,并在执行过程中为函数内部读取过的 observable 属性建立依赖;当这些属性被写入时,tracker 就会再次执行。
每次重跑都会重新收集依赖,所以在不再需要订阅时应及时调用返回的 dispose 函数,否则会产生额外订阅甚至内存泄漏。
描述
接收一个 tracker 函数,如果函数内部有消费 observable 数据,数据发生变化时,tracker 函数会重复执行
签名
ts
interface autorun {
(tracker: () => void, name?: string): void
}用例
<script setup lang="ts">
import { autorun, observable } from '@formily/reactive'
import { onBeforeUnmount, ref } from 'vue'
import { parseNumber, pushLog } from '../observable/shared'
const obs = observable({
aa: 0,
})
const nextValue = ref(1)
const rawValue = ref(obs.aa)
const trackedValue = ref(obs.aa)
const runCount = ref(0)
const active = ref(false)
const logs = ref<string[]>([])
let disposeCurrent: null | (() => void) = null
function syncRawState() {
rawValue.value = obs.aa
}
function start() {
if (active.value)
return
disposeCurrent = autorun(() => {
runCount.value += 1
trackedValue.value = obs.aa
rawValue.value = obs.aa
pushLog(logs, `autorun #${runCount.value}: aa = ${obs.aa}`)
})
active.value = true
}
function stop() {
disposeCurrent?.()
disposeCurrent = null
if (active.value) {
pushLog(logs, 'dispose() called')
}
active.value = false
}
function applyValue() {
obs.aa = nextValue.value
syncRawState()
}
function increment() {
obs.aa += 1
nextValue.value = obs.aa
syncRawState()
}
function reset() {
stop()
obs.aa = 0
nextValue.value = 1
rawValue.value = 0
trackedValue.value = 0
runCount.value = 0
logs.value = []
start()
}
function handleInput(event: Event) {
const target = event.target as HTMLInputElement | null
nextValue.value = parseNumber(target?.value, nextValue.value)
}
start()
onBeforeUnmount(() => stop())
</script>
<template>
<div class="playground">
<p class="hint">
<code>autorun</code> 会立即执行一次,并在依赖的 observable 属性变化后重新执行。点击
<code>dispose()</code> 后,后续写入就不会再触发。
</p>
<div class="inputRow">
<div class="inputGroup">
<label for="autorun-next-value">下一次写入的 aa</label>
<input
id="autorun-next-value"
class="input"
type="number"
:value="nextValue"
@input="handleInput"
>
</div>
</div>
<div class="toolbar">
<button class="btn" @click="applyValue">
obs.aa = nextValue
</button>
<button class="btn" @click="increment">
obs.aa += 1
</button>
<button class="btn" :disabled="active" @click="start">
restart autorun
</button>
<button class="btn secondary" :disabled="!active" @click="stop">
dispose()
</button>
<button class="btn secondary" @click="reset">
reset
</button>
</div>
<div class="metrics">
<div class="metric">
<div class="label">
当前值
</div>
<div class="value">
{{ rawValue }}
</div>
</div>
<div class="metric">
<div class="label">
最近一次追踪值
</div>
<div class="value">
{{ trackedValue }}
</div>
</div>
<div class="metric">
<div class="label">
执行次数
</div>
<div class="value">
{{ runCount }}
</div>
</div>
<div class="metric">
<div class="label">
订阅状态
</div>
<div class="value">
{{ active ? 'active' : 'disposed' }}
</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>autorun 会立即执行一次,并在依赖的 observable 属性变化后重新执行。点击 dispose() 后,后续写入就不会再触发。
当前值
0
最近一次追踪值
0
执行次数
1
订阅状态
active
运行日志
- autorun #1: aa = 0
查看源码
示例代码
ts
import { autorun, observable } from '@formily/reactive'
const obs = observable({})
const dispose = autorun(() => {
console.log(obs.aa)
})
obs.aa = 123
dispose()autorun.memo
描述
在 autorun 中用于创建持久引用数据,仅仅只会受依赖变化而重新执行 memo 内部函数
注意:请不要在 If/For 这类语句中使用,因为它内部是依赖执行顺序来绑定当前 autorun 的
签名
ts
interface memo<T> {
(callback: () => T, dependencies: any[] = []): T
}注意:依赖默认为[],也就是如果不传依赖,代表永远不会执行第二次
用例
<script setup lang="ts">
import { autorun, observable } from '@formily/reactive'
import { onBeforeUnmount, ref } from 'vue'
import { pushLog } from '../observable/shared'
const source = observable({
aa: 0,
})
const sourceValue = ref(source.aa)
const memoValue = ref(0)
const runCount = ref(0)
const active = ref(false)
const logs = ref<string[]>([])
let disposeCurrent: null | (() => void) = null
function start() {
if (active.value)
return
disposeCurrent = autorun(() => {
runCount.value += 1
sourceValue.value = source.aa
const memoState = autorun.memo(() =>
observable({
bb: 0,
}), [])
pushLog(logs, `run #${runCount.value}: aa = ${source.aa}, memo.bb = ${memoState.bb}`)
memoState.bb += 1
memoValue.value = memoState.bb
})
active.value = true
}
function stop() {
disposeCurrent?.()
disposeCurrent = null
active.value = false
}
function incrementSource() {
source.aa += 1
sourceValue.value = source.aa
}
function reset() {
stop()
source.aa = 0
sourceValue.value = 0
memoValue.value = 0
runCount.value = 0
logs.value = []
start()
}
start()
onBeforeUnmount(() => stop())
</script>
<template>
<div class="playground">
<p class="hint">
<code>autorun.memo</code> 会在同一个 autorun 生命周期内复用持久引用。这里的
<code>memo.bb</code> 会随着每次重跑持续累加,而不是每次都从 0 重新开始。
</p>
<div class="toolbar">
<button class="btn" @click="incrementSource">
obs1.aa++
</button>
<button class="btn secondary" @click="reset">
recreate autorun
</button>
</div>
<div class="metrics">
<div class="metric">
<div class="label">
obs1.aa
</div>
<div class="value">
{{ sourceValue }}
</div>
</div>
<div class="metric">
<div class="label">
memo.bb 当前值
</div>
<div class="value">
{{ memoValue }}
</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>autorun.memo 会在同一个 autorun 生命周期内复用持久引用。这里的 memo.bb 会随着每次重跑持续累加,而不是每次都从 0 重新开始。
obs1.aa
0
memo.bb 当前值
1
autorun 次数
1
运行日志
- run #1: aa = 0, memo.bb = 0
查看源码
示例代码
ts
import { autorun, observable } from '@formily/reactive'
const obs1 = observable({
aa: 0,
})
const dispose = autorun(() => {
const obs2 = autorun.memo(() =>
observable({
bb: 0,
})
)
console.log(obs1.aa, obs2.bb++)
})
obs1.aa++
obs1.aa++
obs1.aa++
// 执行四次,输出结果为
/**
* 0 0
* 1 1
* 2 2
* 3 3
*/
dispose()autorun.effect
描述
在 autorun 中用于响应 autorun 第一次执行的下一个微任务时机与响应 autorun 的 dispose
注意:请不要在 If/For 这类语句中使用,因为它内部是依赖执行顺序来绑定当前 autorun 的
签名
ts
interface effect {
(callback: () => void | (() => void), dependencies: any[] = [{}]): void
}注意:依赖默认为[{}],也就是如果不传依赖,代表会持续执行,因为内部脏检查是浅比较
用例
<script setup lang="ts">
import { autorun, observable } from '@formily/reactive'
import { onBeforeUnmount, ref } from 'vue'
import { pushLog } from '../observable/shared'
const obs = observable({
aa: 0,
})
const rawValue = ref(obs.aa)
const trackerRuns = ref(0)
const effectRuns = ref(0)
const cleanupRuns = ref(0)
const active = ref(false)
const logs = ref<string[]>([])
let disposeCurrent: null | (() => void) = null
function start() {
if (active.value)
return
disposeCurrent = autorun(() => {
trackerRuns.value += 1
rawValue.value = obs.aa
pushLog(logs, `tracker #${trackerRuns.value}: aa = ${obs.aa}`)
autorun.effect(() => {
effectRuns.value += 1
pushLog(logs, `effect #${effectRuns.value}: after microtask`)
return () => {
cleanupRuns.value += 1
pushLog(logs, `cleanup #${cleanupRuns.value}: dispose autorun`)
}
}, [])
})
active.value = true
}
function stop() {
disposeCurrent?.()
disposeCurrent = null
active.value = false
}
function increment() {
obs.aa += 1
rawValue.value = obs.aa
}
function reset() {
stop()
obs.aa = 0
rawValue.value = 0
trackerRuns.value = 0
effectRuns.value = 0
cleanupRuns.value = 0
logs.value = []
start()
}
start()
onBeforeUnmount(() => stop())
</script>
<template>
<div class="playground">
<p class="hint">
<code>autorun.effect</code> 会在 autorun 首次执行后的微任务时机运行,并在 autorun
被 dispose 时执行清理函数。
</p>
<div class="toolbar">
<button class="btn" @click="increment">
obs.aa++
</button>
<button class="btn secondary" :disabled="!active" @click="stop">
dispose()
</button>
<button class="btn secondary" @click="reset">
restart autorun
</button>
</div>
<div class="metrics">
<div class="metric">
<div class="label">
aa
</div>
<div class="value">
{{ rawValue }}
</div>
</div>
<div class="metric">
<div class="label">
tracker 次数
</div>
<div class="value">
{{ trackerRuns }}
</div>
</div>
<div class="metric">
<div class="label">
effect 次数
</div>
<div class="value">
{{ effectRuns }}
</div>
</div>
<div class="metric">
<div class="label">
cleanup 次数
</div>
<div class="value">
{{ cleanupRuns }}
</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>autorun.effect 会在 autorun 首次执行后的微任务时机运行,并在 autorun 被 dispose 时执行清理函数。
aa
0
tracker 次数
1
effect 次数
0
cleanup 次数
0
运行日志
- tracker #1: aa = 0
查看源码
示例代码
ts
import { autorun, observable } from '@formily/reactive'
const obs1 = observable({
aa: 0,
})
const dispose = autorun(() => {
const obs2 = autorun.memo(() =>
observable({
bb: 0,
})
)
console.log(obs1.aa, obs2.bb++)
autorun.effect(() => {
obs2.bb++
}, [])
})
obs1.aa++
obs1.aa++
obs1.aa++
// 执行五次,输出结果为
/**
* 0 0
* 1 1
* 2 2
* 3 3
* 3 5
*/
dispose()