@Computed 装饰器只适用于 getter 属性
背景
@Computed() 装饰器的核心作用是将 class 中的 getter 方法转换为 Vue 的 computed 响应式计算属性,从而获得缓存和依赖追踪能力。本文分析了 4 种属性场景,说明为什么 @Computed 只适合用于纯 getter 属性。
场景分析
场景 1:只有 getter 属性 ✅ 适合
class MyService {
count = 0;
@Computed()
get doubleCount() {
return this.count * 2;
}
}这是 @Computed 的标准使用场景。getter 本身是一个纯粹的派生计算——它依赖其他响应式数据,返回一个计算结果。通过 @Computed 将其转换为 Vue computed,可以获得:
- 依赖追踪:当
count变化时,doubleCount自动感知 - 缓存能力:
count未变化时,多次访问doubleCount不会重复计算
场景 2:只有 setter 属性 ❌ 不适合
class MyService {
firstName = '';
lastName = '';
set fullName(value: string) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
}setter 函数的职责是接收一个值,然后更新其他属性。它本身不产生返回值,也不存在"计算结果需要缓存"的需求。Vue 的 computed 核心价值在于缓存 getter 的计算结果,而 setter 只是一个赋值操作的入口,不需要经过 computed 的处理。
此外,从装饰器类型约束的角度来看,@Computed 的类型签名是 ClassGetterDecoratorContext,TypeScript 本身就不允许将其应用于 setter。
场景 3:同时有 getter 和 setter 属性 ❌ 不适合
class MyService {
firstName = '';
lastName = '';
@Computed()
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName(value: string) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
}虽然 getter 部分确实有缓存需求,但 setter 部分的存在引入了不必要的复杂度:
@Computed需要通过原型链遍历查找对应的 setter,增加了实现复杂度- setter 本身只是更新
firstName和lastName,这些属性在 DI 系统中已经是响应式的,赋值操作天然会触发响应式更新 - 将 setter 绑定到
computed的 set 方法上并没有带来额外价值——它只是一个透传
实际上,如果需要这种模式,更好的做法是只对 getter 使用 @Computed,setter 保持为普通方法:
class MyService {
firstName = '';
lastName = '';
@Computed()
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
setFullName(value: string) {
const [first, last] = value.split(' ');
this.firstName = first;
this.lastName = last;
}
}场景 4:accessor 属性 ❌ 不适合
class MyService {
accessor name = 'hello';
}TC39 Stage 3 的 accessor 关键字会自动为属性生成 getter 和 setter,本质上它就是一个普通属性的语法糖。在 DI 系统中,服务实例通过 onActivation 钩子已经被 reactive() 包装为响应式对象,所有普通属性的读写天然具备响应式能力。
对 accessor 属性使用 @Computed 没有意义,因为:
accessor本质上是一个存储值的普通属性,不是派生计算- 响应式系统已经自动追踪了它的变化
- 它不存在"需要缓存的计算逻辑"
关于 Vue computed 支持 get/set 的补充说明
Vue 的 computed 函数确实支持同时传入 get 和 set:
const fullName = computed({
get: () => `${firstName.value} ${lastName.value}`,
set: (value: string) => {
const [first, last] = value.split(' ');
firstName.value = first;
lastName.value = last;
},
});但这并不意味着 @Computed 装饰器也需要支持 setter。原因在于:computed 的 set 并不会"计算"什么——它只是一个回调入口,让你在赋值时执行自定义逻辑。本质上它是个语法糖,让你可以写 obj.fullName = 'John Doe' 而不是 obj.setFullName('John Doe')。
computed 的核心价值在于 get 端的缓存和依赖追踪,set 端只是一个"顺便"提供的便利功能。在 @Computed 装饰器的场景下,支持 set 没有带来实际收益,却增加了实现复杂度(需要遍历原型链查找 setter 并透传)。如果用户确实需要一个"赋值入口",用普通方法就够了。
结论
@Computed 的价值在于将派生计算转换为带缓存的响应式计算属性。只有纯 getter 属性才符合这个语义——它依赖其他数据,通过计算产生一个派生值,并且这个计算结果值得被缓存。
setter、getter+setter 组合、accessor 属性都不适合 @Computed,因为它们要么不涉及计算(setter、accessor),要么引入了不必要的复杂度(getter+setter 组合)。简化 @Computed 的实现,使其只关注纯 getter 场景,能够显著降低代码复杂度。