在Typescript中提取泛型参数



我有一个简单的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链接到代码

最新更新