我有一个简单的IFactory<OUT>
接口和两个派生类。
export interface IFactory<OUT = any> {
create(): OUT;
}
// number implementation
export class NumberFactory implements IFactory<number> { ... }
// string implementation
export class StringFactory implements IFactory<string> { ... }
并且有一个calculate()
方法通过使用给定的模型来构建对象。
对于以下模型;
const model: Record<string, IFactory> = {
num: new NumberFactory(),
str: new StringFactory()
}
calculate()
方法返回如下所示的对象;
const result = calculate(model);
/// result
/// {
/// num: 1,
/// str: "str"
/// }
问题是,我如何知道结果的类型?我尝试内置typescript实用程序,但我无法实现它。
谢谢你的帮助。
如果您将model
注释为Record<string, IFactory>
,那么您已经输了。这导致编译器忘记model
中任何特定的键值关系,而是将其扩展到具有任何可能的键且其属性为任何可能的IFactory
类型的事物。这将是伟大的,如果你想从model
添加/修改/删除属性。否则,您可能应该让编译器为您推断model
的类型:
const model = {
num: new NumberFactory(),
str: new StringFactory()
}
/* const model: {
num: NumberFactory;
str: StringFactory;
} */
现在您有机会让calculate()
跟踪输入类型,以便输出是您所期望的。下面是一个可能的实现:
function calculate<T extends object>(model: { [K in keyof T]: IFactory<T[K]> }) {
return Object.fromEntries(
Object.entries(
model as Record<string, IFactory>
).map(([k, v]) => [k, v.create()])
) as T;
}
在这里,我使用映射类型的推理来将calculate()
的输出视为某些对象类型T
,并将输入视为映射版本,其中每个属性T[K]
都用IFactory
包装以产生IFactory<T[K]>
。
在函数体内部,编译器无法真正跟踪或验证实现是否正确地执行了此操作,因此我使用了一些类型断言来告诉它不必担心。这就把类型安全的责任推给了我,所以我必须小心。
让我们测试一下:
const result = calculate(model);
// const result: {
// num: number;
// str: string;
// }
console.log(result);
/// result
/// {
/// num: 1,
/// str: "str"
/// }
看起来不错。calculate()
接受model
,并将T
推断为{num: number; str: string;}
,并且在运行时也可以实现。
Playground链接到代码