define
Core Idea
define is suitable when you want to declare a domain model explicitly. It lets you assign different observable behaviors, computed properties, and action/batch methods to the same object so the reactive behavior stays aligned with the domain semantics.
Description
Manually define the domain model, you can specify the responsive behavior of specific attributes, or you can specify a method as batch mode
Signature
ts
interface define<Target extends object> {
(
target: Target,
annotations?: {
[key: string]: (...args: any[]) => any
}
): Target
}Annotations
Here, an annotation means the marker function passed to define, used to declare which reactive wrapper should be applied to each property or method.
All Annotations currently supported are:
- observable/observable.deep defines deep hijacking responsive properties
- observable.box defines get/set container
- observable.computed defines calculated properties
- observable.ref defines reference hijacking responsive attributes
- observable.shallow defines shallow hijacking responsive properties
- action/batch defines the batch processing method
Example
<script setup lang="ts">
import { action, define, observable } from '@formily/reactive'
import { ref } from 'vue'
import { formatValue, parseNumber, pushLog, useAutorunEffect } from '../observable/shared'
class DomainModel {
deep = { aa: 1 }
shallow = { count: 0 }
box: any = 0
ref = 'hello'
constructor() {
define(this, {
deep: observable,
shallow: observable.shallow,
box: observable.box,
ref: observable.ref,
total: observable.computed,
action,
})
}
get total() {
return this.deep.aa + this.box.get()
}
action(nextDeep: number, nextBox: number) {
this.deep.aa = nextDeep
this.box.set(nextBox)
}
}
const model = new DomainModel()
const nextDeep = ref(1)
const nextBox = ref(2)
const nextRef = ref(model.ref)
const totalValue = ref(model.total)
const totalRuns = ref(0)
const shallowRuns = ref(0)
const rawDeep = ref(model.deep.aa)
const rawBox = ref(model.box.get())
const rawRef = ref(model.ref)
const rawShallow = ref(model.shallow.count)
const trackedShallow = ref(model.shallow.count)
const snapshot = ref('')
const logs = ref<string[]>([])
function syncRawState() {
rawDeep.value = model.deep.aa
rawBox.value = model.box.get()
rawRef.value = model.ref
rawShallow.value = model.shallow.count
snapshot.value = formatValue({
deep: model.deep,
shallow: model.shallow,
box: model.box.get(),
ref: model.ref,
})
}
useAutorunEffect(() => {
totalRuns.value += 1
totalValue.value = model.total
pushLog(logs, `autorun(total) #${totalRuns.value}: total = ${model.total}`)
})
useAutorunEffect(() => {
shallowRuns.value += 1
trackedShallow.value = model.shallow.count
pushLog(logs, `autorun(shallow) #${shallowRuns.value}: count = ${model.shallow.count}`)
})
syncRawState()
function runAction() {
model.action(nextDeep.value, nextBox.value)
syncRawState()
}
function applyRef() {
model.ref = nextRef.value
syncRawState()
}
function mutateShallowNested() {
model.shallow.count += 1
syncRawState()
}
function replaceShallow() {
model.shallow = { count: model.shallow.count + 1 }
syncRawState()
}
function reset() {
model.deep.aa = 1
model.box.set(0)
model.ref = 'hello'
model.shallow = { count: 0 }
nextDeep.value = 1
nextBox.value = 2
nextRef.value = 'hello'
syncRawState()
}
function handleDeepInput(event: Event) {
const target = event.target as HTMLInputElement | null
nextDeep.value = parseNumber(target?.value, nextDeep.value)
}
function handleBoxInput(event: Event) {
const target = event.target as HTMLInputElement | null
nextBox.value = parseNumber(target?.value, nextBox.value)
}
function handleRefInput(event: Event) {
const target = event.target as HTMLInputElement | null
nextRef.value = target?.value ?? nextRef.value
}
</script>
<template>
<div class="playground">
<p class="hint">
This demo combines several <code>define</code> annotations:
<code>deep</code>, <code>shallow</code>, <code>ref</code>, <code>box</code>, and
<code>computed</code>.
</p>
<div class="inputRow">
<div class="inputGroup">
<label for="define-next-deep-en">deep value for action</label>
<input
id="define-next-deep-en"
class="input"
type="number"
:value="nextDeep"
@input="handleDeepInput"
>
</div>
<div class="inputGroup">
<label for="define-next-box-en">box value for action</label>
<input
id="define-next-box-en"
class="input"
type="number"
:value="nextBox"
@input="handleBoxInput"
>
</div>
<div class="inputGroup">
<label for="define-next-ref-en">ref value</label>
<input
id="define-next-ref-en"
class="input"
type="text"
:value="nextRef"
@input="handleRefInput"
>
</div>
</div>
<div class="toolbar">
<button class="btn" @click="runAction">
action(nextDeep, nextBox)
</button>
<button class="btn" @click="applyRef">
model.ref = nextRef
</button>
<button class="btn" @click="mutateShallowNested">
shallow.count += 1
</button>
<button class="btn" @click="replaceShallow">
replace shallow
</button>
<button class="btn secondary" @click="reset">
reset
</button>
</div>
<div class="metrics">
<div class="metric">
<div class="label">
computed(total)
</div>
<div class="value">
{{ totalValue }}
</div>
</div>
<div class="metric">
<div class="label">
total autorun runs
</div>
<div class="value">
{{ totalRuns }}
</div>
</div>
<div class="metric">
<div class="label">
shallow autorun runs
</div>
<div class="value">
{{ shallowRuns }}
</div>
</div>
</div>
<div class="panels">
<div class="panel">
<div class="panelTitle">
Current Values
</div>
<pre>{{ snapshot }}</pre>
</div>
<div class="panel">
<div class="panelTitle">
Tracked Values
</div>
<pre>{{
formatValue({
total: totalValue,
shallowCount: trackedShallow,
ref: rawRef,
deep: rawDeep,
box: rawBox,
shallowRaw: rawShallow,
})
}}</pre>
</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> This demo combines several define annotations: deep, shallow, ref, box, and computed.
computed(total)
1
total autorun runs
1
shallow autorun runs
1
Current Values
{
"deep": {
"aa": 1
},
"shallow": {
"count": 0
},
"box": 0,
"ref": "hello"
} Tracked Values
{
"total": 1,
"shallowCount": 0,
"ref": "hello",
"deep": 1,
"box": 0,
"shallowRaw": 0
} Run Log
- autorun(shallow) #1: count = 0
- autorun(total) #1: total = 1
查看源码
Example Code
ts
import { action, autorun, define, observable } from '@formily/reactive'
class DomainModel {
deep = { aa: 1 }
shallow = {}
box = 0
ref = ''
constructor() {
define(this, {
deep: observable,
shallow: observable.shallow,
box: observable.box,
ref: observable.ref,
computed: observable.computed,
action,
})
}
get computed() {
return this.deep.aa + this.box.get()
}
action(aa, box) {
this.deep.aa = aa
this.box.set(box)
}
}
const model = new DomainModel()
autorun(() => {
console.log(model.computed)
})
model.action(1, 2)
model.action(1, 2) // Repeat calls will not respond repeatedly
model.action(3, 4)