在 Vue 3 的响应式系统中,computed 是一个非常重要的功能,它用于创建基于依赖自动更新的计算属性。本文将通过分析源码,理解 computed 的底层实现逻辑,帮助你从源码层面掌握它的原理。
一、computed 的基本使用
在使用层面上,computed 有两种常见用法:
1. 只读计算属性
1const count = ref(1) 2const plusOne = computed(() => count.value + 1) 3 4console.log(plusOne.value) // 输出 2 5plusOne.value++ // 报错,因只读 6
2. 可写计算属性
1const count = ref(1) 2const plusOne = computed({ 3 get: () => count.value + 1, 4 set: (val) => { count.value = val - 1 } 5}) 6 7plusOne.value = 3 8console.log(count.value) // 输出 2 9
这两种形式在底层源码中都会通过一个统一的类 ComputedRefImpl 来实现。
二、核心类:ComputedRefImpl
ComputedRefImpl 是 Vue 3 中计算属性的核心实现类,它实现了响应式依赖追踪和懒执行机制。
1. 成员属性解析
1export class ComputedRefImpl<T = any> implements Subscriber { 2 _value: any = undefined // 缓存计算后的值 3 dep: Dep = new Dep(this) // 依赖收集容器 4 readonly __v_isRef = true // 标记为 ref 类型 5 readonly __v_isReadonly: boolean // 是否只读 6 deps?: Link // 依赖链表(订阅者) 7 flags: EffectFlags = EffectFlags.DIRTY // 标志位,初始为脏值 8 globalVersion: number = globalVersion - 1 // 全局版本号 9 isSSR: boolean // 是否在服务端渲染环境中 10 next?: Subscriber = undefined // 下一个订阅者 11 12 // 调试钩子 13 onTrack?: (event: DebuggerEvent) => void 14 onTrigger?: (event: DebuggerEvent) => void 15 16 constructor( 17 public fn: ComputedGetter<T>, 18 private readonly setter: ComputedSetter<T> | undefined, 19 isSSR: boolean, 20 ) { 21 this[ReactiveFlags.IS_READONLY] = !setter 22 this.isSSR = isSSR 23 } 24} 25
关键点:
flags:使用EffectFlags管理状态,如DIRTY表示需要重新计算。dep:内部维护依赖列表,供其他响应式对象追踪。_value:缓存上次计算的值,实现懒计算。__v_isReadonly:若没有传入 setter,则为只读计算属性。
三、value 的访问逻辑
1get value(): T { 2 const link = this.dep.track() // 依赖追踪 3 refreshComputed(this) // 若脏则重新计算 4 if (link) { 5 link.version = this.dep.version 6 } 7 return this._value 8} 9
这里是 computed 的核心机制:
- 依赖追踪:通过
dep.track()让当前计算属性被其他响应式数据订阅。 - 惰性求值:只有在访问
.value时,才会执行 getter 重新计算。 - 版本同步:
link.version确保依赖的版本号一致,以判断是否需要重新计算。
四、value 的设置逻辑
1set value(newValue) { 2 if (this.setter) { 3 this.setter(newValue) 4 } else if (__DEV__) { 5 warn('Write operation failed: computed value is readonly') 6 } 7} 8
- 若定义了
setter,则允许外部修改计算属性值; - 否则在开发环境中发出警告,提示为只读属性。
五、依赖更新与通知机制
当依赖的响应式数据发生变化时,notify() 会被调用:
1notify(): true | void { 2 this.flags |= EffectFlags.DIRTY // 标记为脏值 3 if (!(this.flags & EffectFlags.NOTIFIED) && activeSub !== this) { 4 batch(this, true) // 批量更新依赖 5 return true 6 } 7} 8
这里的关键是:
- 标记为 “脏”,下次访问时会重新计算;
- 通过
batch批量更新,避免重复通知。
六、computed 函数入口
外层的 computed 函数是一个工厂方法:
1export function computed<T>( 2 getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>, 3 debugOptions?: DebuggerOptions, 4 isSSR = false, 5) { 6 let getter: ComputedGetter<T> 7 let setter: ComputedSetter<T> | undefined 8 9 if (isFunction(getterOrOptions)) { 10 getter = getterOrOptions 11 } else { 12 getter = getterOrOptions.get 13 setter = getterOrOptions.set 14 } 15 16 const cRef = new ComputedRefImpl(getter, setter, isSSR) 17 18 if (__DEV__ && debugOptions && !isSSR) { 19 cRef.onTrack = debugOptions.onTrack 20 cRef.onTrigger = debugOptions.onTrigger 21 } 22 23 return cRef as any 24} 25
这里做了两件事:
- 根据参数判断是只读还是可写计算属性;
- 创建
ComputedRefImpl实例; - 如果处于开发模式,还会绑定调试钩子(
onTrack、onTrigger)。
七、总结
Vue 3 中的 computed 实现基于以下关键机制:
| 机制 | 作用 |
|---|---|
| 惰性求值 (Lazy Evaluation) | 仅在访问时计算结果 |
| 缓存结果 (Caching) | 若依赖未变则返回缓存值 |
| 依赖追踪 (Dependency Tracking) | 自动感知依赖变化 |
| 脏标记 (Dirty Flag) | 控制何时重新计算 |
| 批量更新 (Batching) | 提高性能,减少重复通知 |
从源码可以看到,computed 实际上是一个特殊的 ref,但拥有更多的依赖追踪与缓存机制,是 Vue 响应式系统中非常精妙的一环。
本文内容由人工智能生成,仅供学习与参考使用,请在实际应用中结合自身情况进行判断。
《深入解析 Vue 3 源码:computed 的底层实现原理》 是转载文章,点击查看原文。
