如何为.reduce返回的类实例对象标注TypeScript正确类型?
TypeScript批量实例化类的类型标注解决方法
需求背景
我有多个需要动态实例化的类,想把它们存在变量里,通过遍历批量创建实例——以后只要往存储结构里加新类,就能自动完成实例化。同时要给最终的实例对象classesInstances标注正确的类型,让每个属性对应对应类的实例。
初始尝试(数组存储类)
一开始用数组存类,代码如下:
// 单独文件中的类导出与存储 import { ClassOne } from '@data/ClassOne'; import { ClassTwo } from '@data/ClassTwo'; export const myClasses = [ ClassOne, ClassTwo, ] as const; // 遍历实例化 const classesInstances = myClasses.reduce((acc, CurClass) => { acc[_.camelCase(CurClass.name)] = new CurClass({ globalOptions, sharedCache, }); return acc; }, {});
但没法给classesInstances标注出{ classOne: ClassOne实例, classTwo: ClassTwo实例 }这样的精准类型,用Record<string, ...>只能得到模糊的联合类型。
改用对象存储类后的进展与问题
后来改用对象存储类,终于能定义出正确的实例类型,但reduce循环内部报错:
// 第一个文件:存储类的对象 import { ClassOne } from '@data/ClassOne'; import { ClassTwo } from '@data/ClassTwo'; export const myClasses = { classOne: ClassOne, classTwo: ClassTwo, }; // 第二个文件:定义实例类型 type ClassesInstances = { [key in keyof typeof myClasses]: InstanceType<(typeof myClasses)[key]>; }; // 遍历实例化,但reduce内部报错 const myClassesInstances = Object.entries(myClasses).reduce( (acc, [key, MyClass]) => { // 报错:类型“ClassesInstances”上不存在类型为“string”的参数的索引签名。 acc[key] = new MyClass({ context, cache, }); return acc; }, {} as ClassesInstances, ); // 注:myClassesInstances的最终类型是正确的,只是reduce内部有报错
尝试把key断言为keyof ClassesInstances后,又出现新报错:Type 'ClassOne | ClassTwo' is not assignable to type 'ClassOne & ClassTwo'.
解决方法
问题根源是Object.entries会把键的类型推断为string,TypeScript没法跟踪每个键对应的具体类。下面是三种可行的解决方式:
方式1:精准类型断言
在reduce内部给key和实例做精准的类型断言,确保类型匹配:
const myClassesInstances = Object.entries(myClasses).reduce( (acc, [key, MyClass]) => { const typedKey = key as keyof typeof myClasses; acc[typedKey] = new MyClass({ context, cache }) as InstanceType<(typeof myClasses)[typeof typedKey]>; return acc; }, {} as ClassesInstances, );
方式2:改用for...in循环
for...in能更好地保留键的类型信息,避免Object.entries的类型丢失问题:
const myClassesInstances: ClassesInstances = {} as ClassesInstances; for (const key in myClasses) { if (myClasses.hasOwnProperty(key)) { myClassesInstances[key] = new myClasses[key]({ context, cache }); } }
方式3:封装泛型工具函数
如果需要复用逻辑,可以写一个泛型函数自动推导类型,不用手动定义ClassesInstances:
function instantiateClasses<T extends Record<string, new (...args: any[]) => any>>( classes: T, instanceArgs: ConstructorParameters<T[keyof T]>[0] ): { [K in keyof T]: InstanceType<T[K]> } { const instances = {} as { [K in keyof T]: InstanceType<T[K]> }; for (const key in classes) { instances[key] = new classes[key](instanceArgs); } return instances; } // 使用时直接传类对象和实例参数,自动推导类型 const myClassesInstances = instantiateClasses(myClasses, { context, cache });
内容的提问来源于stack exchange,提问作者tcK
相关产品推荐
相关产品推荐

