TypeScript推断受限条件键类型的值类型时出现问题

TypeScript推断受限条件键类型的值类型时出现问题,typescript,Typescript,假设我有以下实体模型: interface Entity { id: string; } interface Member extends Entity { group: Group | string; } interface Group extends Entity { members: (Member | string)[]; } 我将分别检索组和成员实体的列表。检索时,实体将包含对其他实体的字符串引用。例如,成员实体将具有一个组属性,其中包含该成员所属组的ID 现在,在检

假设我有以下实体模型:

interface Entity {
  id: string;
}

interface Member extends Entity {
  group: Group | string;
}

interface Group extends Entity {
  members: (Member | string)[];
}
我将分别检索组和成员实体的列表。检索时,实体将包含对其他实体的字符串引用。例如,成员实体将具有一个组属性,其中包含该成员所属组的ID

现在,在检索之后,我想通过将字符串引用替换为它们引用的实体来水合/膨胀实体。例如,以下调用应将成员实体中的组引用替换为实际的组对象:

现在,我想将该调用“group”中的第三个参数限制为只允许可能引用组实体的属性名,因此我编写了以下类型定义:

type Ref<T, S> = { [K in keyof T]: S extends T[K] ? K : never }[keyof T];
不幸的是,这会引发编译器错误:

Type 'S | T[{ [K in keyof T]: S extends T[K] ? K : never; }[keyof T]]' is not assignable to type 'T[{ [K in keyof T]: S extends T[K] ? K : never; }[keyof T]]'.
  Type 'S' is not assignable to type 'T[{ [K in keyof T]: S extends T[K] ? K : never; }[keyof T]]'.
    Type 'Entity' is not assignable to type 'T[{ [K in keyof T]: S extends T[K] ? K : never; }[keyof T]]'.(2322)
我相信,有了TypeScript丰富的类型系统,应该完全有可能想出一个优雅的解决方案,但我似乎无法正确地实现它。我错过了什么

对于那些想尝试一下的人,这里有一个建议


请注意,我正在寻找一个优雅的表意字体解决方案,而不是一个强迫编译器的黑客。如果我想这样做,我不妨编写简单的JavaScript。

通常,当实现中的泛型函数中有条件类型和映射类型时,TS将很难始终跟踪这些类型的所有含义。通常,仍然包含未解析类型参数的条件类型或映射类型只能分配给自身,因此S不能分配给T[Ref]

有一种方法可以确保函数类型的实现是安全的,但这是以开发人员经验为代价的:

function link<S extends Entity, K extends PropertyKey>(target: Array<Record<K, string | S>>, source: S[], ref: K) { 
  const lookup = new Map(source.map<[string, S]>(entity => [entity.id, entity]));

  target.forEach(entity => {
    const value = entity[ref];
    entity[ref] = typeof value === 'string' ? lookup.get(value) || value : value;
  });
}

通常,当在实现中的泛型函数中有条件类型和映射类型时,TS将很难始终跟踪这些类型的所有含义。通常,仍然包含未解析类型参数的条件类型或映射类型只能分配给自身,因此S不能分配给T[Ref]

有一种方法可以确保函数类型的实现是安全的,但这是以开发人员经验为代价的:

function link<S extends Entity, K extends PropertyKey>(target: Array<Record<K, string | S>>, source: S[], ref: K) { 
  const lookup = new Map(source.map<[string, S]>(entity => [entity.id, entity]));

  target.forEach(entity => {
    const value = entity[ref];
    entity[ref] = typeof value === 'string' ? lookup.get(value) || value : value;
  });
}

将签名更改为:

function link<T extends Entity, K extends keyof T, S extends T[K] & Entity>(
  target: T[], source: S[], ref: K
) {

TypeScript无法推断像S扩展t[{[K in keyof t]:S扩展t[K]?K:never;}[keyof t]]那样复杂的关系。

将签名更改为:

function link<T extends Entity, K extends keyof T, S extends T[K] & Entity>(
  target: T[], source: S[], ref: K
) {
TypeScript无法推断像S扩展t[{[K in keyof t]:S扩展t[K]?K:never;}[keyof t]]那样复杂的关系

function link<T extends Entity, K extends keyof T, S extends T[K] & Entity>(
  target: T[], source: S[], ref: K
) {