TypeScript为函数动态添加getter/setter后访问报TS2339错误的解决思路
可行解决方案
核心思路是利用TypeScript的泛型自动推导能力,捕获动态挂载属性的来源结构,在不硬编码键名、不滥用any的前提下,让TS能识别函数上挂载的动态属性。
具体实现步骤
- 取消给源对象(示例里的
obj)预先标注宽泛的索引签名类型,让TS自动推导对象字面量的具体键名和对应值类型——如果一开始就标注{[key:string]:string},TS会丢失对具体字面量键的识别能力,后续推导就会失效。 - 保持原有运行时的循环挂载逻辑不变,TypeScript本身无法静态识别
Object.defineProperty的运行时副作用,这部分是正常的。 - 在所有动态属性挂载完成后,给函数做一次精确的类型交叉断言:把原函数的类型,和源对象遍历生成的可读写属性类型做交叉,这部分的键名和值类型完全由TS自动推导,不需要手动硬编码。
示例代码
// 自动推导obj的键为'blah' | 'test',值为string const obj = { blah: "ok", test: "yes" }; // 原函数保持定义 const rawFunc = (a: string): void => {}; // 原有动态挂载逻辑不变 for (const key in obj) { Object.defineProperty(rawFunc, key, { get() { return obj[key]}, set(a: string) { obj[key] = a; } }); } // 类型断言:交叉原函数类型和自动推导的属性类型 const func = rawFunc as typeof rawFunc & { [K in keyof typeof obj]: typeof obj[K] }; // 以下访问都不会再报TS2339错误,且类型完全精确 console.log(func.blah); // 类型推导为string console.log(func.test); // 类型推导为string
更优雅的封装方式
可以把动态挂载的逻辑封装成泛型工具函数,后续使用时连手动写类型交叉的步骤都可以省略,TS会自动完成类型推导:
function attachPropsFromSource< FuncType extends (...args: unknown[]) => unknown, SourceType extends Record<string, unknown> >( func: FuncType, sourceObj: SourceType ): FuncType & { [K in keyof SourceType]: SourceType[K] } { for (const key in sourceObj) { Object.defineProperty(func, key, { get() { return sourceObj[key] }, set(value: SourceType[typeof key]) { sourceObj[key] = value; } }); } return func as FuncType & { [K in keyof SourceType]: SourceType[K] }; } // 使用时完全自动推导类型,无硬编码、无any const obj = { blah: "ok", test: "yes" }; const func = attachPropsFromSource((a: string): void => {}, obj); console.log(func.blah); // 正常访问,类型为string
注意事项
- 不要用
as any做类型转换,否则会完全丢失TS的类型检查能力。上面的实现中,所有属性的类型都和源对象保持一致,可正常触发类型校验。 - 如果你的源对象确实需要支持任意动态字符串键,只需要在交叉类型中补充对应的索引签名即可,比如:
这部分依然不需要硬编码具体键名。const func = rawFunc as typeof rawFunc & { [key: string]: string };
内容的提问来源于stack exchange,提问作者vanowm
相关产品推荐
相关产品推荐

