computed在vue3中的实现
使用方式:
computed(() => {console.log(state.a)})
核心代码:
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
class ComputedRefImpl<T> {
private _value!: T
private _dirty = true
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true;
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
if (this._dirty) {
this._value = this.effect()
this._dirty = false
}
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newValue: T) {
this._setter(newValue)
}
}
由上可知
- 计算属性触发求值时,就会触发get value()
- 通过dirty开关,来实现当计算属性的值不发生改变时,不会执行if内的内容,直接返回value
- 所以当第一次求值时,通过this.effect()来求值
详细看看this.effect()
主要做了const effect = function reactiveEffect(): unknown { if (!effect.active) { return options.scheduler ? undefined : fn() } // effectStack是这个栈应该是收集effect, if (!effectStack.includes(effect)) { // 当前effect的deps中去除effect cleanup(effect) try { enableTracking() effectStack.push(effect) activeEffect = effect return fn() } finally { effectStack.pop() resetTracking() activeEffect = effectStack[effectStack.length - 1] } } } as ReactiveEffect effect.options = options //options中有scheduler- 将当前的依赖effect推入activeEffect中
- 执行fn(),也就是getter(),触发内部响应值来收集当前effect依赖,就是当前effect
- 最后finally,effect推出,acitveEffect为栈顶元素,这个值往往是渲染函数render
- 接着执行这个track(toRaw(this), TrackOpTypes.GET, 'value'),这个是实现计算属性来收集自己的value值的依赖,这时计算属性value就将渲染函数render收集到自己的依赖中
- 当内部元素的值发现改变时,触发依赖更新会执行下面的代码
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
// 给computed使用的 当computed中的值改变时执行下面
//if (!this._dirty) {
// this._dirty = true
// trigger(toRaw(this), TriggerOpTypes.SET, 'value')
// }
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
- 因为计算属性的effect是有effect.options.scheduler的值的
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
})
- 所以执行上的scheduler()
- 当内部元素值方式改变时要重新求值,所以将dirty变成true,同时触发toRaw(this)的依赖,这个依赖就是渲染函数render,
- toRaw(this) 这个就是自己
export function toRaw<T>(observed: T): T { // 这种写法替换if-else 厉害 return ( (observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed ) } - 因为在执行渲染函数时就一定会触发计算属性的求值,从而实现重新赋值以及重新收集依赖
总结
相对于vue2,之前是通过计算属性内部的依赖值来收集计算属性的watcher以及计算属性触发的渲染函数render的依赖;而vue3可以明显的看到是通过自身的value来收集自身触发的渲染函数render的effect,实现上更加巧妙和解耦