持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情
关键函数-createAnnotation
这个函数从变量名看是创建注解的意思,在代码中多次被其他 API 调用,我们先来看一下这个函数使用的地方。
在项目名为”annotations”的文件夹中,发现 observable 上的方法:box、computed、ref 等都是通过调用 createAnnotation 的方式进行定义的。比如在 shallow 的定义中。
//@formily/reactive/src/annotations/shallow
export const shallow: IObservable = createAnnotation(
({ target, key, value }) => {
const store = {
value: createObservable(target, key, target ? target[key] : value, true),
}
function get() {
// 代码省略
}
function set(value: any) {
// 代码省略
}
if (target) {
Object.defineProperty(target, key, {
set,
get,
enumerable: true,
configurable: false,
})
return target
}
return store.value
}
)
// @formily/reactive/src/shallow
import * as annotations from './annotations'
observable.shallow = annotations.shallow
而 observable.shallow 存在两种调用方式。
-
作为 observable 上的方法输出为外部调用的API。
import { observable, autorun } from '@formily/reactive' const obs = observable.shallow({ aa: { bb: 111, }, }) autorun(() => { console.log(obs.aa.bb) }) obs.aa.bb = 222 // 不会响应 obs.aa = { bb: 333 } // 可以响应如果是这样的调用方式,shallow 的 maker 函数中,其实 target 和 key 参数都是 null,只是传递了 value 参数(目的在于将 value 改造为浅观察模式的 observable 对象)。
-
其次就是在自定义的领域模型中,这一块会在之后的 model & define API 部分讲解,可以有个大概印象,在这种情况下,shallow 的 maker 函数中,target 就是领域模型对象,key 是被标注的变量名,标注(annotation)就是 shallow,需要被改造为 shallow 模式的 observable 就是被标注的变量值。
这种情况,还有一个地方也值得注意,那就是在这里使用了
defineProperty对 target 的 get、set分别定义。当使用这个领域模型的被标注的属性时,会触发 get 或者 set。get 和 set 的实现和 createObservable 中代理对象 proxy 的 handler 的实现差不多,这里之所以使用 defineProperty 就是为了在领域对象内外引用时访问的都是其本身。返回 proxy 的方式适用于在被代理对象的外部进行引用。
上面主要简单介绍了 createAnnotation 的使用方式,并且顺带看到了 observable.shallow 的具体实现。
而createAnnotation 的定义如下:
export const createAnnotation = <T extends (visitor: IVisitor) => any>(
maker: T
) => {
const annotation = (target: any): ReturnType<T> => {
return maker({ value: target })
}
if (isFn(maker)) {
annotation[MakeObservableSymbol] = maker
}
return annotation
}
它接收一个 maker 函数,返回一个(接收单个 target 参数作为 maker 函数参数中的 value 并)执行 maker 的函数。
并且,返回的函数上有一个 symbol 类型的标志属性,属性值存储 maker 函数。这个属性的作用似乎就在讲“我是一个 annotation” 😲,当然,这个标志在之后鉴定 annotation 非常有用。对于这种高阶函数,一定要想想他被调用的过程。那么我们回到 shallow 的定义,可以回答 shallow 是什么,shallow 接受一个 value 参数,使用内部既定的 maker 逻辑,返回 maker 执行的结果。
总结一下,
- createAnnotation 负责创建 annotation。
- observale.shallow、observable.deep 等都是 annotation。
- annotation 是一个包含了既定的 maker 逻辑的函数,接受 value 参数,返回 maker 执行结果。
- maker 函数是将 value 参数改造为某种形式的 observable 对象。
- annotation 函数上包含了一个标志属性,表明这是一个“annotation” 😅。
关键API-batch
从这一部分开始,我们将学习 reactive 提供的关键 API 的实现,首先就从 batch 开始吧。
首先来看一下 example!
import { observable, autorun, batch } from '@formily/reactive'
const obs = observable({})
autorun(() => {
console.log(obs.aa, obs.bb, obs.cc, obs.dd)
})
batch(() => {
obs.bb = 321
obs.dd = 'dddd'
})
这个例子很简单,含义为 batch 中对 observable 对象的操作只会触发一次 autorun 中的执行。其实到这就可以猜到,batch API 的实现应该和源码中的 batchStart、batchEnd 等脱不了关系。
果然,我们可以发现 batch 的定义如下:
// @formily/reactive/src/batch
export const batch: IBatch = createBoundaryAnnotation(batchStart, batchEnd)
至于这个 createBoundaryAnnotation 是什么呢,我们转到其定义。
export const createBoundaryAnnotation = (
start: (...args: any) => void,
end: (...args: any) => void
) => {
const boundary = createBoundaryFunction(start, end)
const annotation = createAnnotation(({ target, key }) => {
target[key] = boundary.bound(target[key], target)
return target
})
boundary[MakeObservableSymbol] = annotation
boundary.bound[MakeObservableSymbol] = annotation
return boundary
}
首先,函数内部调用 createBoundary 创建了一个 boundary 变量。createBoundary 定义如下:
export const createBoundaryFunction = (
start: (...args: any) => void,
end: (...args: any) => void
) => {
function boundary<F extends (...args: any) => any>(fn?: F): ReturnType<F> {
let results: ReturnType<F>
try {
start()
if (isFn(fn)) {
results = fn()
}
} finally {
end()
}
return results
}
boundary.bound = createBindFunction(boundary)
return boundary
}
export const createBindFunction = <Boundary extends BoundaryFunction>(
boundary: Boundary
) => {
function bind<F extends (...args: any[]) => any>(
callback?: F,
context?: any
): F {
return ((...args: any[]) =>
boundary(() => callback.apply(context, args))) as any
}
return bind
}
从而可以解释这个 boundary 是接收一个位于 start 和 end 之间执行的参数函数,并返回该参数函数执行结果的函数。而 batch 函数就是一个 boundary。
因此,我们可以解释上面的例子,在执行被 batch 包裹的这段代码时,
() => {
obs.bb = 321
obs.dd = 'dddd'
}
首先会执行 batchStart,此时处于 isBatching 状态,触发的 Reaction 均被存入 PendingReactions 中等待执行。换句话说,修改obs.bb后并不会触发 autorun 。最后执行 batchEnd,将暂存 Reactions 依次取出,由于 ArraySet 的特殊性,两个相同的 Reaction 只有最开始的那次被添加到 PendingReactions 中,所以只存在 autorun 的一次触发。
当然,在 createBoundary 中还有关于 annotation 的额外逻辑。具体而言,就是这个 boundary.bound,绑定 callback 和调用对象。这一部分更适合在 model & define 中讲解,当 batch 用作 annotation 时(一般是在 define 中使用时),这个 annotation 内部的逻辑(也是其 maker )是:
- 使用 boundary 的形式调用 callback(包含对 observable 对象属性的修改操作)。什么是 boundary 的形式呢,就是让 callback 的执行位于 start 和 end 函数之间执行。
- 调用 callback 的对象 this 指向 annotation 接受的 target 参数。