autorun
Core Idea
autorun is the most direct form of reaction. It runs the tracker immediately and records every observable property that gets read during execution. When any of those properties is written later, the tracker runs again.
Dependencies are collected again on every rerun, so when you no longer need the subscription you should call the returned dispose function to avoid stale subscriptions or memory leaks.
Description
Receive a tracker function, if there is observable data in the function, the tracker function will be executed repeatedly when the data changes
Signature
interface autorun {
(tracker: () => void, name?: string): void
}Example
<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> runs immediately and reruns whenever one of its tracked observable
properties changes. After <code>dispose()</code>, further writes stop triggering it.
</p>
<div class="inputRow">
<div class="inputGroup">
<label for="autorun-next-value-en">next aa value</label>
<input
id="autorun-next-value-en"
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">
current value
</div>
<div class="value">
{{ rawValue }}
</div>
</div>
<div class="metric">
<div class="label">
last tracked value
</div>
<div class="value">
{{ trackedValue }}
</div>
</div>
<div class="metric">
<div class="label">
run count
</div>
<div class="value">
{{ runCount }}
</div>
</div>
<div class="metric">
<div class="label">
subscription
</div>
<div class="value">
{{ active ? 'active' : 'disposed' }}
</div>
</div>
</div>
<div>
<div class="sectionTitle">
Run Log
</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 runs immediately and reruns whenever one of its tracked observable properties changes. After dispose(), further writes stop triggering it.
- autorun #1: aa = 0
Example Code
import { autorun, observable } from '@formily/reactive'
const obs = observable({})
const dispose = autorun(() => {
console.log(obs.aa)
})
obs.aa = 123
dispose()autorun.memo
Description
Used in autorun to create persistent reference data, only re-execute memo internal functions due to dependency changes
Note: Please do not use it in If/For statements, because it depends on the execution order to bind the current autorun
Signature
interface memo<T> {
(callback: () => T, dependencies: any[] = []): T
}Note: The default dependency is [], that is, if the dependency is not passed, it means that the second time will never be executed
Example
<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> keeps a persistent reference for the lifetime of the current
autorun. Here <code>memo.bb</code> keeps increasing across reruns instead of resetting to 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">
current memo.bb
</div>
<div class="value">
{{ memoValue }}
</div>
</div>
<div class="metric">
<div class="label">
autorun runs
</div>
<div class="value">
{{ runCount }}
</div>
</div>
</div>
<div>
<div class="sectionTitle">
Run Log
</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 keeps a persistent reference for the lifetime of the current autorun. Here memo.bb keeps increasing across reruns instead of resetting to 0.
- run #1: aa = 0, memo.bb = 0
Example Code
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++
// Execute four times, the output result is
/**
* 0 0
* 1 1
* twenty two
* 3 3
*/
dispose()autorun.effect
Description
In autorun, it is used to respond to the next micro task timing of autorun's first execution and the dispose of responding to autorun
Note: Please do not use it in If/For statements, because it depends on the execution order to bind the current autorun
Signature
interface effect {
(callback: () => void | (() => void), dependencies: any[] = [{}]): void
}Note: The default dependency is [{}], that is, if the dependency is not passed, the representative will continue to execute, because the internal dirty check is a shallow comparison
Example
<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> runs on the microtask after the first autorun execution and calls
its cleanup function when the autorun is disposed.
</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 runs
</div>
<div class="value">
{{ trackerRuns }}
</div>
</div>
<div class="metric">
<div class="label">
effect runs
</div>
<div class="value">
{{ effectRuns }}
</div>
</div>
<div class="metric">
<div class="label">
cleanup runs
</div>
<div class="value">
{{ cleanupRuns }}
</div>
</div>
</div>
<div>
<div class="sectionTitle">
Run Log
</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 runs on the microtask after the first autorun execution and calls its cleanup function when the autorun is disposed.
- tracker #1: aa = 0
Example Code
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++
// Execute five times, the output result is
/**
* 0 0
* 1 1
* twenty two
* 3 3
* 3 5
*/
dispose()