You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript Stage 3字段装饰器改造:监听字段变更失效问题排查

问题解析与正确实现

关于context.access.set的obj参数

obj参数是当前被修改属性的类实例引用。Stage 3装饰器基于类的元编程模型,作用于类的字段定义而非单个实例,因此setter需要明确知道是哪个实例的属性在被修改,obj就是该实例的指向。

你的实现存在的问题

  1. setter签名不匹配:context.access.set要求的签名是(obj: T, value: V) => void,你的代码缺少obj参数,导致类型错误和逻辑失效。
  2. 无限递归风险:直接在setter中执行this[context.name] = newVal会再次触发setter,陷入死循环。
  3. 实例状态共享:你用初始化时的value变量存储属性值,所有实例会共享同一个值,导致状态混乱,无法独立维护每个实例的属性状态。

正确的Stage 3字段装饰器实现

方案一:基于实例私有存储(推荐)

这种方式为每个实例独立存储属性值,避免状态共享,逻辑清晰:

function watchChange<T, V>(target: any, context: ClassFieldDecoratorContext<T, V>) {
  // 生成唯一私有符号,用于存储每个实例的属性值,避免命名冲突
  const storageKey = Symbol(`${String(context.name)}_internal`);

  return function (initialValue: V) {
    // 初始化当前实例的私有存储
    this[storageKey] = initialValue;

    // 为当前实例定义带监听逻辑的属性访问器
    Object.defineProperty(this, context.name, {
      get() {
        return this[storageKey];
      },
      set(newVal: V) {
        console.log(`${String(context.name)} changed from ${this[storageKey]} to ${newVal}`);
        this[storageKey] = newVal;
      },
      configurable: true,
      enumerable: true,
    });

    // 返回undefined,让原字段被我们定义的访问器覆盖
    return undefined;
  };
}

方案二:基于context.access扩展原访问器

如果需要保留原字段的访问器逻辑,可以通过包裹原setter实现:

function watchChange<T, V>(target: any, context: ClassFieldDecoratorContext<T, V>) {
  const originalSet = context.access.set;
  const storageKey = Symbol(`${String(context.name)}_internal`);

  return function (initialValue: V) {
    this[storageKey] = initialValue;

    // 自定义setter,接收实例和新值
    const customSet = (obj: T, newVal: V) => {
      console.log(`${String(context.name)} changed from ${obj[storageKey]} to ${newVal}`);
      obj[storageKey] = newVal;
      // 如果存在原setter,调用原逻辑
      if (originalSet) originalSet(obj, newVal);
    };

    // 自定义getter
    const customGet = (obj: T) => {
      const val = obj[storageKey];
      // 如果存在原getter,调用原逻辑
      return context.access.get ? context.access.get(obj) : val;
    };

    // 替换原访问器
    context.access.set = customSet;
    context.access.get = customGet;

    return initialValue;
  };
}

使用示例

class Test {
  @watchChange
  public name = "initial";
}

const test = new Test();
test.name = "updated"; // 输出:name changed from initial to updated
console.log(test.name); // 输出:updated

内容的提问来源于stack exchange,提问作者Barosanu240

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 20:33:32