装饰器
@Computed 装饰器
When "reactive" uses "class" and "computed", the "computed" attribute will not Reactive
这个 issue 说明了为什么不能直接在 class 的属性上赋值 computed 对象。因为 computed 初始化的时机是非常早的,此时 this 还没有变成响应式对象。 所以 computed 不起作用。
可选的一种方案就是在 this 初始化之后,手动调用 init 方法,在 init 方法中在初始化 computed 相关的属性。 如果是使用@PostConstruct 装饰器,此时也不需要在业务代码中手动调用 init 方法,init 方法会自动执行。
另一种更好的方案就是使用 getter 属性再配合@Computed 装饰器,这样就既能享受 computed 带来的相响应式能力,又能解决 getter 方法没有缓存的问题。
MarkRaw 装饰器
因为本库默认会将整个实例对象转化为响应式对象,但有时候也会期望指定特定的属性不参与响应式追踪。
所以这里的本意是实现一个 MarkRaw 装饰器,作用是使用markRaw函数处理属性值,从而该属性就不再是响应式的。
✅ 已解决(4.0.0 版本)
@Raw 装饰器已在 4.0.0 版本中正式实现。基于 TC39 Stage 3 的 field 装饰器和 accessor 装饰器,@Raw 可以直接在装饰器层面拦截属性的初始化和赋值,不再需要与 container.onActivation 耦合,也不存在下文讨论的"无法在装饰器执行阶段获取实例对象"的问题。
用法示例:
import { Raw } from '@kaokei/use-vue-service';
class DemoService {
// 普通 field 装饰器
@Raw
public bigObject = SomeBigObject;
// auto-accessor 装饰器
@Raw
accessor chartInstance = {};
}详细用法请参考 API 文档 - Raw。
以下为历史讨论内容,记录了早期(基于 Legacy experimentalDecorators)尝试实现该装饰器时遇到的问题和思考过程,保留作为参考。
历史讨论(已解决)
以下内容基于旧版 TypeScript experimentalDecorators 装饰器规范讨论,在 4.0.0 版本中已通过 TC39 Stage 3 装饰器彻底解决。
class DemoService {
@MarkRaw
public bigObject = SomeBigObject;
}当时决定放弃这个装饰器,有两个原因:
- 可以直接使用
markRaw函数代替
class DemoService {
public bigObject = markRaw(SomeBigObject);
}- 实现 @MarkRaw 的成本有一点点高,而且必须和本库的
container.onActivation强制耦合。
在当时尝试的实现方案中,比较重要的一点是旧版属性装饰器只能在类的原型上定义属性描述符。
Object.defineProperty(prototype, property, descriptor)
这样有一个问题就是当我们访问/设置实例属性时,并不会触发原型上的属性描述符。
那最终只能想办法在实例对象上定义属性描述符。
Object.defineProperty(instance, property, descriptor)
但是旧版属性装饰器在执行阶段并不能获取到实例对象,因为此时还没有实例化对象。
当时能想到的实现方案就是在 @MarkRaw 装饰器中收集有哪些属性需要使用 markRaw 函数进行处理。 然后在container.onActivation中获取到实例对象后,遍历之前收集到的所有属性,针对每个属性创建属性描述符。
在实现可行性方面是没有问题的,关键在于这个装饰器只能在本库的上下文中使用,并不能在任意的类中使用,并不具有通用性。
4.0.0 版本解决方案:TC39 Stage 3 装饰器规范中,field 装饰器可以通过返回
initializer函数来拦截字段初始化值,accessor 装饰器可以通过返回get/set函数来拦截属性的读写。这使得@Raw装饰器可以在不依赖container.onActivation的情况下独立工作,具备完全的通用性。