【发布时间】:2020-10-14 08:47:21
【问题描述】:
假设我有以下实体模型:
interface Entity {
id: string;
}
interface Member extends Entity {
group: Group | string;
}
interface Group extends Entity {
members: (Member | string)[];
}
我将分别检索Group 和Member 实体的列表。在检索时,实体将包含对其他实体的字符串引用。例如,Member 实体将有一个 group 属性,其中包含该成员所属组的 ID。
现在,在检索后,我想通过将字符串引用替换为它们所引用的实体来对实体进行水合/膨胀。例如,以下调用应将 Member 实体中的 group 引用替换为实际的 Group 对象:
link(members, groups, 'group');
现在,我想将该调用中的第三个参数 ('group') 限制为仅允许可能引用 Group 实体的属性名称,因此我编写了以下类型定义:
type Ref<T, S> = { [K in keyof T]: S extends T[K] ? K : never }[keyof T];
这可以正常工作,例如type X = Ref<Member, Group> 将评估为 group。
方法实现可能如下所示:
function link<T extends Entity, S extends Entity>(target: T[], source: S[], ref: Ref<T, S>) {
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; // error
});
}
不幸的是,这会引发编译器错误:
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 丰富的类型系统,应该完全有可能想出一个优雅的解决方案,但我似乎无法做到正确。我在这里错过了什么?
这里有一个link to the TypeScript Playground,供想要尝试的人使用。
请注意,我正在寻找一种优雅的意识形态 TypeScript 解决方案,而不是为了强制编译器的黑客攻击。如果我愿意,我不妨编写纯 JavaScript。
【问题讨论】:
标签: typescript