TypeScript意外交集

时间:2019-05-01 08:55:35

标签: typescript

我有一个“模型”的注册表,当我从注册表中选择一个模型并对其调用方法时,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 }
};

enter image description here

Playground Link

2 个答案:

答案 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中没有错误,因为在进行类型检查实现时假设Nstring-推断getName的类型为

Model<string>.getName: (options: { __type: string; }) => string