带着问题看源码:
1.computed 是如何实现响应式的?
2.computed 是如何实现计算结果缓存的?
function computed(getterOrOptions, debugOptions, isSSR = false) {
let getter
let setter
// 判断第一个参数是不是一个函数
const onlyGetter = isFunction(getterOrOptions)
// 构造 setter 和 getter 函数
if (onlyGetter) {
getter = getterOrOptions
// 如果第一个参数是一个函数,那么就是只读的
setter = __DEV__
? () => {
console.warn('Write operation failed: computed value is readonly')
}
: NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 构造 ref 响应式对象
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
// 返回响应式 ref
return cRef
}
入参
getterOrOptions:ComputedGetter<T> | WritableComputedOptions<T>
export interface WritableComputedOptions<T> {
get: ComputedGetter<T>
set: ComputedSetter<T>
}
export type ComputedGetter<T> = (...args: any[]) => T
返回值
ComputedRef 继承于 WritableComputedRef 继承于 Ref
会根据 传入的参数 将判断是否onlyGetter 我们只分析 isSSR为false 的 情况
class ComputedRefImpl {
public dep = undefined
private _value
public readonly effect
//表示 ref 类型
public readonly __v_isRef = true
//是否只读
public readonly [ReactiveFlags.IS_READONLY] = false
//用于控制是否进行值更新(代表是否脏值)
public _dirty = true
// 缓存
public _cacheable
constructor(
getter,
_setter,
isReadonly,
isSSR
) {
// 把 getter 作为响应式依赖函数 fn 参数
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
// 触发更新
triggerRefValue(this)
}
})
// 标记 effect 的 computed 属性
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
get value() {
const self = toRaw(this)
// 依赖收集
trackRefValue(self)
// 脏值则进行更新
if (self._dirty || !self._cacheable) {
//step2
self._dirty = false
// 更新值
self._value = self.effect.run()!
}
//step4
return self._value
}
// 执行 setter
set value(newValue) {
this._setter(newValue)
}
}
结合代码来分析 可以debug 验证
例子
const App = {
setup() {
const age = ref('20')
onMounted(()=>{
// step5
age.value = '2'
})
// step1
const info = computed(()=>{
// step3
return age.value + '岁'
})
obj.name = 'ws'
age.value = '29'
return ()=>{
return h('div',[info.value+info.vlaue])
}
}
}
代码执行到 mark-one 会去执行 computed —> 初始化 ComputedRefImpl 得到一个 Ref 的 ComputedRef 子类
当 App 组件 挂载 执行 render 函数的时候 会访问 到 info.value。这个时候 会走到 ComputedRefImpl get value 方法里。此时 _dirty 为 true 会执行
get value() {
const self = toRaw(this)
// 依赖收集
trackRefValue(self)
// 脏值则进行更新
if (self._dirty || !self._cacheable) {
self._dirty = false
// 更新值
self._value = self.effect.run()!
}
return self._value
}
先进行依赖收集(收集的target 是 info(ref对象),对应的effect 是 组件实例的effect 副作用函数就是 componentUpdateFn),在执行 ComputedRefImpl 实例上的 effect.run -----> 也就是 getter ,传入 computed 的函数
执行 传入的 computed 的函数
const info = computed(()=>{
return age.value + '岁'
})
这时候访问 age.value 会进行依赖收集(收集的target 是 age 对应的effect是 ComputedRefImpl 实例上的 effect 后面简称 ComputedEffect )。
返回 执行的 结果 info.value 此时为 '20岁'
render 函数 访问 第二个 info.value 时
_dirty 此时已经是 false, 会直接 return self._value 不会 再去计算
render 完后 执行 onmounted 钩子 修改 age.value 派发更新 上面step3 依赖收集的effect 是 computedEffect 会执行 effect.schdule
this.effect = new ReactiveEffect(getter, () => {
// 执行这里的方法
if (!this._dirty) {
this._dirty = true
// 触发更新
triggerRefValue(this)
}
})
先将 _dirty 设置为 true 再触发 之前 step2 收集的 组件effect 更新 会重新 执行 render 又会访问到 info.value —> 再到 get 中 由于 _dirty 已经被设置为 true
会执行 依赖收集(重复的job 会自动过滤掉) 执行 重新计算 effect.run() ---->getter
获得最新的值