[Vue 源码] Vue 3.2 - Computed 原理
代码运行结果
代码示例
<script src="./dist/reactivity.global.js"></script>
<body>
<div id="app"></div>
<script>
let { effect, reactive, ref, shallowRef, toRef, computed } = VueReactivity
let state = reactive({ firstName: 'Tom', lastName: 'Benjamin' }) // 将普通的类型 转化成一个对象,这个对象中有value属性 指向原来的值
const fullName = computed({
// getter
get() {
return state.firstName + ' ' + state.lastName
},
// setter
set(newValue) {
;[state.firstName, state.lastName] = newValue.split(' ')
}
})
effect(() => {
app.innerHTML = fullName.value
})
setTimeout(() => {
fullName.value = 'Cyan Benjamin'
}, 2000)
挂载阶段
第一:第一篇文章详细介绍了 reactive 创建响应式流程,我们这里默认已经创建好了响应式对象 obj。 [Vue 源码] Vue 3.2 - Reactive 原理
执行 第二句代码:
const fullName = computed({
// getter
get() {
return state.firstName + ' ' + state.lastName
},
// setter
set(newValue) {
;[state.firstName, state.lastName] = newValue.split(' ')
}
})
第二:调用 computed 函数, 在 computed 函数中初始化 getter 和 setter函数,然后通过 new ComputedRefImpl 创建 computedRef 对象。
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
debugOptions?: DebuggerOptions,
isSSR = false
) {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
getter = getterOrOptions.get
setter = getterOrOptions.set
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
return cRef as any
}
第三: 执行 ComputedRefImpl 的 constructor 方法
- 创建 ReactiveEffect 对象 赋值给 computed 对象的 effect 属性。创建过程 [Vue 源码] Vue 3.2 - Reactive 原理 有详细说明。
- 默认创建好了 ReactiveEffect 对象 后 将 computedRef 的 getter 作为 ReactiveEffect 对象对象的 fn 属性。
- 创建ReactiveEffect 对象时 传入第二个参数 -> scheduler 函数。
- 初始化 dirry 属性 false 来表示是否为脏数据。
- 返回 computed 对象
dirty 表示是否为脏数据来缓存计算属性,第一次或者说默认取值是脏数据执行 effect.run 方法取值,二次/多次取值 dirty 不是脏数据, 取缓存数据,等待依赖变了 scheduler 将 dirty 置为 true, 下次取值会重新执行 effect.run 方法来计算缓存值。
public _dirty = true
public _cacheable: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean,
isSSR: boolean
) {
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = this._cacheable = !isSSR
this[ReactiveFlags.IS_READONLY] = isReadonly
}
第三句代码调用:effect(() => {app.innerHTML = fullName.value})
第四:
- effect 函数调用,创建 ReactiveEffect 对象,执行 ReactiveEffect 对象上的 Run 方法,将 activeEfffect 对象只为当前创建的ReactiveEffect 对象,清除上一次的依赖列表, 执行
() => {app.innerHTML = fullName.value}
- 执行 fullname.value 触发 fullname.value 的 getter 函数。
get value() {
trackRefValue(self)
if (self._dirty || !self._cacheable) {
self._dirty = false
self._value = self.effect.run()!
}
return self._value
}
- getter 函数中 调用 trackRefValue(self)。
- trackRefValue 中调用 trackEffects 函数,使用 computedRef 对象的 dep 属性来收集依赖对象。也就是将 activeEffect 对象也就是 effect 产生的 ReacttiveEffects 传入到 computedRef 对象的依赖对象列表当中去。
- 执行
export function trackRefValue(ref: RefBase<any>) {
if (shouldTrack && activeEffect) {
ref = toRaw(ref)
if (__DEV__) {
trackEffects(ref.dep || (ref.dep = createDep()), {
target: ref,
type: TrackOpTypes.GET,
key: 'value'
})
} else {
trackEffects(ref.dep || (ref.dep = createDep()))
}
}
}
-
getter 函数中 调用 self.effect.run()。也就是调用 computed 的 getter 或者说 ReactiveEffects 对象的 fn属性,也就是
get() { return state.firstName + ' ' + state.lastName }
-
执行 obj.firstName 和 obj.lastName 触发 obj 的 getter ,将当前 computed 的 ReactEffect 对象收集到 firstName, lastName 属性的 effects Set 集合当中。通过 Reflect.get 方法得到值。
const targetMap = {
{ firstName: 'Tom', lastName: 'Benjamin' }: {
firstName: [ReactEffect],
lastName: [ReactEffect]
}
}
- 返回计算值赋值给 computed 对象的 _value 属性。
至此初次渲染完毕。
更新阶段
第一:fullname.value = "Cyan Benjamin", 触发 computedRef 对象 value 属性的 setter 方法,调用 this.settter 函数,传入新值。
set value(newValue: T) {
this._setter(newValue)
}
第二:
- setter 函数中通过 Reflect.set 给响应式对象赋值
- 触发响应式对象 的 setter 操作, 通过 Reflect.set() 修改 lastname 和 firstname。
- 继而触发 trigger 操作,不过这一次并没有执行该 ReactiveEffect 对象的run 方法,而是优先执行了 scheduler 方法。
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
第三:scheduler 方法中执行 triggerRefValue ,
var scheduler = () => {
if (!this._dirty) {
this._dirty = true
triggerRefValue(this)
}
}
-
在 triggerRefValue 中 调用了 triggerEffects 函数,trigerEffects 执行了 triggerEffect 函数。
-
triggerEffect 函数中遍历 computedRef 对象的依赖列表。
-
依次然后调用依赖对象的 run 方法
-
run 方法中 先通过 cleanEffect 函数清空依赖。 然后让 activeEffect 指向当前的 ReactiveEffect 对象(computed 创建的 ReactiveEffect 对象), 执行依赖对象的 fn 函数,也就是 getter 方法, 也就是
get() { return state.firstName + ' ' + state.lastName }
-
getter 方法中拿到最新值,同时 stata.firstName , state.lastName 又一次触发了 响应式对象 触发 getter 操作, 再一次跟踪activeEffect 依赖对象(computed 创建的 ReactiveEffect 对象)。
-
返回并渲染最新值,等待下一次更新。
自此更新完毕
转载自:https://juejin.cn/post/7208732065696989243