我有以下方法,用于用相同类型的其他对象扩展现有的键值映射(例如db查询的结果)。
export function extendWith<
T extends { id: string | number },
O =
| (T["id"] extends string | number ? Record<T["id"], T> : never)
| (T["id"] extends string | number ? Partial<Record<T["id"], T>> : never)
>(obj: O, vals: T | T[]): O {
const extended = { ...obj };
const values = Array.isArray(vals) ? vals : [vals];
for (const val of values) {
if (val !== undefined) {
const prop = val["id"];
if (
typeof prop === "string" ||
typeof prop === "number" ||
typeof prop === "symbol"
) {
(extended as any)[prop] =
prop in obj ? { ...(obj as any)[prop], ...val } : val;
}
}
}
return extended;
}当我按如下方式调用它时,一切都很好,即我在最后一行得到一个类型记录错误,正确地说明我要传入的对象的name类型是错误的。
interface Photo {
id: number;
name: string;
}
const photos: { [key: number]: Photo } = {
1: { id: 1, name: "photo-1" },
2: { id: 2, name: "photo-2" }
};
const extendedPhotos = extendWith<Photo>(photos, { id: 4, name: 3 });现在,当我在最后一行的<Photo>调用中删除显式参数extendWith时,类型记录错误就消失了。我认为这与类型抄本泛型推理有关。
有没有人知道怎样才能达到正确的推论?任何让我走上正确道路的建议都是非常感谢的!
一个这里提供可玩的沙箱。
发布于 2022-01-11 12:53:52
首先,您的第一个重载看起来是不必要的,因为您是说,当id是字符串或数字时,返回的对象要么是Partial<O>,要么是完整的O。O在映射到Partial<O>类型时总是有效的,所以您只需说它的类型是Partial<O>。
关于推断,如果让TypeScript推断类型,它将使用您的输入来推断函数的输出。您所要求的似乎是,函数的第二个参数必须是Photo类型的,这不是推理,除非您传递一个Photo类型的变量,否则不能推断它。要想推断出TS,您需要将最后一行替换为以下内容:
const myPhoto: Photo { id: 4, name: 'my-photo' };
const extendedPhotos = extendWith<Photo>(photos, myPhoto);这样,TypeScript就可以使用输入值中的信息来推断输出值。
发布于 2022-01-11 20:44:17
这是可以做到的(尽管这个例子需要扩展):
type Index = string | number;
type ObjectWithIdIndex = { id: Index };
function extendWith<O extends Record<Index, ObjectWithIdIndex>>(obj: O, v: O[keyof O]): O {
(obj as Record<Index, ObjectWithIdIndex>)[v.id] = v;
return obj;
}这些参数的类型可简化为:
obj是一个具有id属性值的对象。我们只能通过接受扩展Record<Index, ObjectWithIdIndex>的类型来限制这一点。因此,下面应该会给出一个类型错误:extendWith(null, { id: 3, name: "photo-4" });
extendWith({ abc: 1 }, { id: 3, name: "photo-4" });v与obj值的类型相同。我们可以用O[keyof O]来限制这一点。因为keyOf是obj属性的合并,所以O[keyof O]是这些属性的值的联合。以下内容还应提供一个类型错误:interface Photo { id: number; name: string; }
const photos: Record<number, Photo> = {
1: { id: 1, name: "photo-1" },
2: { id: 2, name: "photo-2" }
};
extendWith(photos, { id: 4, name: "photo-4", abc: 2 });
extendWith(photos, { id: 4, name: 1 });
extendWith(photos, { name: "abc" });
extendWith(photos, null);当调用extendWith时,类型记录将推断出比Record<Index, ObjectWithIdIndex>更具体的obj类型。这样做的结果是:
obj值的类型,然后约束v。obj分配新的属性了,因为类型记录不知道obj是否仍然是可扩展的记录类型(例如。{ a: 1 }是Record<string, number>的一个子类型,但是新的属性不能分配给它)。但是,我们可以将obj转换回它更通用的类型,然后扩展它。https://stackoverflow.com/questions/70666598
复制相似问题