我有一个“模型”的注册表,当我从注册表中选择一个模型并对其调用方法时,TypeScript期望所有已注册模型中的参数都相交。
为简便起见,我使用伪方法“ getName”重现了此错误。
export class Model<N extends string> {
public name: N;
constructor(name: N) {
this.name = name;
}
public getName = (options: { __type: N }) => options.__type;
}
export const Book = new Model("Book");
export const User = new Model("User");
export const modelRegistry = { Book, User };
export type ModelRegistry = typeof modelRegistry;
export const makeModel = <N extends keyof ModelRegistry>(name: N) => (
options: Parameters<ModelRegistry[N]["getName"]>[0],
) => {
const model = modelRegistry[name];
return model.getName(options); // <-- bug: TS expects this to be { __type: User } & { __type: Book }
};
答案 0 :(得分:1)
我认为这里的问题是,编译器在尝试调用时不知道如何(或选择不)将泛型ModelRegistry[N]["getName"]
解释为与类型N
相关联,并且而是将N
扩展为完整的联合类型keyof ModelRegistry
。因此,它将model.getName
视为不同参数类型的函数类型的并集。在TypeScript 3.3之前,这种联合只是not callable at all。在TypeScript 3.3中添加了支持,以使这些功能可以called with an intersection of the parameters for each function in the union。这比“不可赎回”要好,但是仍然有很多不足之处,特别是在您涉及correlated types的情况下。
在这种情况下,最简单的解决方法是接受比编译器更聪明的选择,并接受assert类型的期望。为了演示,我将使用as any
使编译器静音:
export const makeModel = <N extends keyof ModelRegistry>(name: N) => (
options: Parameters<ModelRegistry[N]["getName"]>[0],
): ReturnType<ModelRegistry[N]["getName"]> => {
const model = modelRegistry[name];
return model.getName(options as any) as any; // I'm smarter than the compiler
};
这应该为您工作...如果需要,可以将any
断言严格化为更适用的类型。请注意,由于编译器不太可能将其找出来,因此我已手动将咖喱makeModel
调用的返回类型注释为ReturnType<ModelRegistry[N]["getName"]
。
无论如何,希望能有所帮助。祝你好运!
答案 1 :(得分:0)
另一种解决此错误的方法是定义makeModel
的通用版本,该版本可以在任意注册表上运行,然后为一个注册表实例定义特定版本:
export const makeAnyModel = <Registry extends { [N in string]: Model<N> }>
(registry: Registry, name: keyof Registry) => (
options: Parameters<Registry[keyof Registry]["getName"]>[0],
) => {
const model = registry[name];
return model.getName(options);
};
export const makeModel = <N extends keyof ModelRegistry>(name: N) =>
makeAnyModel(modelRegistry, name);
makeAnyModel
中没有错误,因为在进行类型检查实现时假设N
为string
-推断getName
的类型为
Model<string>.getName: (options: { __type: string; }) => string