vue 3 computed
1. 类结构定义
class ComputedRefImpl<T> {
// 核心状态
private _value!: T // 缓存的计算结果
private _dirty = true // 脏值标志
public readonly effect: ReactiveEffect<T>
// 是否支持设置(只读判断)
public readonly __v_isReadonly: boolean
constructor(
getter: () => T,
private readonly _setter: (newValue: T) => void,
isReadonly: boolean
) {
// 创建一个 effect 并配置调度器
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true // 依赖变化时标记为需要重新计算
trigger(toRaw(this), TriggerOpTypes.SET, 'value') // 触发依赖更新
}
})
this.effect.computed = this // 关联 effect 和 computed 实例
this.__v_isReadonly = isReadonly
}
get value(): T {
// 关键:触发依赖收集和重新计算
if (this._dirty) {
this._value = this.effect.run()
this._dirty = false
}
track(toRaw(this), TrackOpTypes.GET, 'value') // 收集当前依赖(如组件渲染函数)
return this._value
}
set value(newValue: T) {
this._setter(newValue) // 调用用户提供的 setter
}
}
2. 关键实现细节
(1) 依赖追踪机制
- 计算属性本身是被观察者:当其他代码(如模板)访问
computed.value
时,会触发track
收集对computedRef
的依赖。 - 计算属性内部是观察者:通过
this.effect
订阅其getter
中使用的响应式数据的变化。
(2) 调度器(Scheduler)
() => {
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), TriggerOpTypes.SET, 'value')
}
}
- 当计算属性依赖的响应式数据变化时,调度器会被触发:
- 标记
_dirty = true
:表示下次访问.value
时需要重新计算。 - 调用
trigger
:通知依赖此计算属性的副作用(如组件重新渲染)更新。
- 标记
(3) 懒计算(Lazy Evaluation)
- 计算属性的求值延后到 真正访问
.value
时进行:if (this._dirty) { // 只有当依赖变化时才会重新计算 this._value = this.effect.run() // 执行 getter this._dirty = false }
3. 使用示例与流程
(1) 创建计算属性
const count = ref(0)
const double = computed(() => count.value * 2)
过程:
- 实例化
ComputedRefImpl
- 创建
ReactiveEffect
包裹 getter,并配置调度器函数
(2) 访问计算属性(首次)
console.log(double.value) // 0
流程:
_dirty
为true
→ 执行this.effect.run()
- 运行
() => count.value * 2
得到 0 - 收集
count.value
到此effect
的依赖中 _dirty
标记为false
- 触发
track
,将当前渲染函数等副作用订阅到double
的变化
(3) 依赖更新
count.value++
流程:
count
变更触发double
的effect
的调度器- 标记
_dirty = true
- 调用
trigger
通知订阅者(如组件重新渲染)
(4) 再次访问
console.log(double.value) // 2 (触发重新计算)
4. 性能优化手段
- 缓存机制:重复访问
.value
但依赖未变化时直接返回_value
。 - 批量更新:若多次修改依赖值,最终只重新计算一次。
- 无效计算规避:若计算属性未被用到,其内部
effect
不会被激活。
与模板的联动
当模板中使用计算属性时:
<template>{{ double }}</template>
- 组件渲染时会访问
double.value
→ 触发getter
- 模板成为计算属性的订阅者,当
count
变化时:- 调度器标记
_dirty = true
trigger
触发组件重新渲染- 渲染时再次访问
double.value
→ 重新计算并缓存
- 调度器标记
源码中的实际调用
当调用 computed()
函数时:
function computed<T>(
getter: () => T,
debugOptions?: DebuggerOptions
): ComputedRef<T>
function computed<T>(
options: WritableComputedOptions<T>,
debugOptions?: DebuggerOptions
): WritableComputedRef<T>
function computed<T>(
getterOrOptions: any,
debugOptions?: DebuggerOptions
) {
let getter: () => T
let setter: (v: T) => void
// 处理 getter/setter 参数
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = __DEV__ ? () => { /* 警告 */ } : NOOP
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 创建 ComputedRefImpl 实例
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
) as any
}
总结
ComputedRefImpl
通过组合以下特性实现计算属性:
- 响应式 Effect 系统:追踪
getter
中的依赖。 - 脏值标记(_dirty):优化重新计算时机。
- 缓存机制(_value):避免重复计算。
- 调度器(Scheduler):控制依赖变更时的触发逻辑。
- 懒求值(Lazy):不访问时不会运行计算。
这种设计保证了计算属性既高效响应依赖变化,又避免不必要的性能消耗。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY