如何优化含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
相关产品推荐
相关产品推荐

