用法
有两种使用方法,一种只有get,其返回值也是只读的,另一种是可读可写
- get:传入一个getter函数,computed的返回值为getter函数返回值经过ref封装后,并且是只读的。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // error
- set: 也可以传入一个对象,包含get和set两个函数,这时,computed的返回值就可写了。
源码中关于可读可写的判断也是根据我们传入的参数来判断,我们可以看到,如果传入的是一个函数,默认就是getter函数,自然是个可读的,另外如果传入对象,这里注意 ,并不是传入函数那就是可写的,你也得有set函数,这在源码中也有提现:
// ...
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set //这对应构造函数的readonly
) as any
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
//...
}
通过函数重载来进行两种用法的类型校验:
// read-only
function computed<T>(getter: () => T): Readonly<Ref<Readonly<T>>>
// writable
function computed<T>(options: { get: () => T; set: (value: T) => void }): Ref<T>
用法不是很复杂,其实watch和watchEffect都类似,下面我们深入看一下返回这个Ref,也就是
ComputedRefImpl这个类
ComputedRefImpl
先贴一下源码:
class ComputedRefImpl<T> {
// some initial var...
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// ..
}
get value() {
// ...
}
set value(newValue: T) {
// ...
}
}
里面定义了一些变量,除了构造函数还有get和set,里面还调用了effect、track、trigger方法,源码中专门放在一个文件effect.ts中,这里面是专门处理副作用的,包括对应依赖的存储
这里调用effect对getter进行包装,就是为了创建缓存,用文档的原话来说:计算属性是基于它们的反应依赖关系缓存的,计算属性只在相关响应式依赖发生改变时它们才会重新求值
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
这里传入了两个配置项,一个lazy,一个scheduler,一开始我也不知道这个scheduler是干嘛的,但是这个单词翻译过来就是调度程序,所谓调度程序就是指定如何运行副作用函数的,另外里面还有一个trigger函数,来触发SET操作的,所以顾名思义就是一个执行set操作,另外通过_dirty这个变量来控制是否读取缓存还是新值
get value() {
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
然后我们进入effect这个方法中查看,主要通过createReativeEffect这个函数来创建
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
// ....
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
我们可以看到 返回一个reactiveEffect函数,这个函数上包括一下属性:
_isEffect: true
id: number
active: boolean
raw: () => T
deps: Array<Dep>
options: ReactiveEffectOptions
allowRecurse: boolean
track & trigger
副作用的依赖项的数据结构大致如下:
weakmap:(target, Map(key,Set[effect,effect]) )
target----->key------>dep
track用于追踪收集依赖,trigger用于触发响应。