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

如何优化含Proxy的TypeScript装饰器的类型定义?

TypeScript Proxy装饰器的类型优化问题

我编写了一个基于Proxy的装饰器,用于从localStorage读取指定属性的值,代码如下:

function StoredValues<T>(storageKey: string, storedProperties: Array<keyof T>) {
  function keyForProperty(property: string | symbol) {
    return `${storageKey}.${property.toString()}`;
  }

  function shouldUseStorage(property: string | symbol): boolean {
    return storedProperties.includes(property as keyof T); // 能否移除这个类型断言?
  }

  function actualDecorator(target: Function) { 
    // 有没有更合适的返回类型?
    const f: any = function (...args: any[]) { 
      return new Proxy(Reflect.construct(target, args), {
        get(target, property) {
          if (shouldUseStorage(property)) {
            const fromStorage = localStorage.getItem(keyForProperty(property));
            if (fromStorage) {
              console.log('From storage', fromStorage);
              // 警告!这里会返回字符串(原属性可能是数字等非字符串类型)
              return fromStorage;
            }
          }
          console.log('Pass through', property);
          return target[property];
        },
        set(target: any, property: string | symbol, value: any): boolean {
          target[property] = value;
          if (shouldUseStorage(property)) {
            localStorage.setItem(keyForProperty(property), value);
          }
          console.log('Set', property, 'to', value);
          return true;
        },
      });
    };
    return f;
  }
  return actualDecorator;
}

我对当前的类型定义非常不满意,尤其是大量使用any的部分。尝试过多种类型声明方式都无法解决问题,比如使用new Proxy<T>()时,编译器会抛出错误:Decorator function return type '(...args: any[]) => Bar' is not assignable to type 'void | typeof Bar'。


类型优化方案

1. 修正装饰器的构造函数类型

类装饰器的目标类型应该是new (...args: any[]) => T(而非泛泛的Function),返回的构造函数也必须与原类构造函数类型兼容,这样TypeScript才能正确推断实例类型。

2. 移除shouldUseStorage的类型断言

将storedProperties转换为Set<keyof T>,配合类型守卫实现无断言的属性检查:

const storedPropsSet = new Set(storedProperties);

function isStoredProperty(property: string | symbol): property is keyof T {
  return typeof property === 'string' && storedPropsSet.has(property as keyof T);
}

// 后续直接调用isStoredProperty即可,无需额外断言

3. 给Proxy和构造函数指定精确类型

明确构造函数的返回类型为原类构造函数类型,Proxy代理的实例类型为T,同时修复get/set方法的类型约束:

function actualDecorator(target: new (...args: any[]) => T) { 
  const decoratedConstructor: new (...args: any[]) => T = function (...args: any[]) { 
    const instance = Reflect.construct(target, args);
    return new Proxy<T>(instance, {
      get(target: T, property) {
        if (isStoredProperty(property)) {
          // ...逻辑
        }
        return target[property as keyof T];
      },
      set(target: T, property, value: T[keyof T]): boolean {
        if (isStoredProperty(property)) {
          // ...逻辑
        }
        target[property as keyof T] = value;
        return true;
      },
    });
  };
  // 保留原类的名称和原型链,避免破坏类标识
  Object.defineProperty(decoratedConstructor, 'name', { value: target.name });
  decoratedConstructor.prototype = target.prototype;
  return decoratedConstructor;
}

4. 修复localStorage的类型转换问题

原代码直接存储/读取原始值会导致非字符串类型丢失,需要用JSON.stringify和JSON.parse处理序列化:

  • 存值:localStorage.setItem(storageKey, JSON.stringify(value));
  • 取值:return JSON.parse(fromStorage) as T[keyof T];

完整优化后的代码

function StoredValues<T>(storageKey: string, storedProperties: Array<keyof T>) {
  const storedPropsSet = new Set(storedProperties);

  function keyForProperty(property: string) {
    return `${storageKey}.${property}`;
  }

  function isStoredProperty(property: string | symbol): property is keyof T {
    return typeof property === 'string' && storedPropsSet.has(property as keyof T);
  }

  function actualDecorator(target: new (...args: any[]) => T) { 
    const decoratedConstructor: new (...args: any[]) => T = function (...args: any[]) { 
      const instance = Reflect.construct(target, args);
      return new Proxy<T>(instance, {
        get(target: T, property) {
          if (isStoredProperty(property)) {
            const storageKey = keyForProperty(property as string);
            const fromStorage = localStorage.getItem(storageKey);
            if (fromStorage !== null) {
              console.log('From storage', fromStorage);
              try {
                return JSON.parse(fromStorage) as T[keyof T];
              } catch (e) {
                console.error('Failed to parse stored value', e);
                return target[property];
              }
            }
          }
          console.log('Pass through', property);
          return target[property as keyof T];
        },
        set(target: T, property, value: T[keyof T]): boolean {
          if (isStoredProperty(property)) {
            const storageKey = keyForProperty(property as string);
            try {
              localStorage.setItem(storageKey, JSON.stringify(value));
            } catch (e) {
              console.error('Failed to serialize value for storage', e);
            }
          }
          target[property as keyof T] = value;
          console.log('Set', property, 'to', value);
          return true;
        },
      });
    };
    Object.defineProperty(decoratedConstructor, 'name', { value: target.name });
    decoratedConstructor.prototype = target.prototype;
    return decoratedConstructor;
  }
  return actualDecorator;
}

关于之前的Proxy类型错误

类装饰器要求返回的构造函数必须与原构造函数类型兼容,或者返回void。之前的代码未明确指定构造函数的返回类型,导致TypeScript无法推断兼容性。优化后的代码明确了构造函数的类型与原类一致,解决了该错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 21:25:21