我有以下方法用于擴展現有的鍵值映射 - 例如,資料庫查詢的結果 - 具有相同型別的其他物件。
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呼叫中洗掉顯式引數時,打字稿錯誤消失了。我認為這與打字稿通用推理有關。
有誰知道實作推理正確的方法?非常感謝任何讓我走上正確道路的提示!
可在此處使用的沙盒。
uj5u.com熱心網友回復:
首先,看起來你的第一個多載是不必要的,因為你說當id是一個字串或一個數字時,回傳的物件要么是 要么Partial<O>是完整的O。O映射到型別時總是有效的Partial<O>,所以你可以說它的型別是Partial<O>.
關于推斷,如果您讓 TypeScript 推斷型別,它將使用您的輸入來推斷函式的輸出。您似乎要求的是函式的第二個引數必須是 type Photo,這不是推理,除非您向它傳遞一個已經是型別的變數,否則無法推斷Photo。要讓 TS 推斷,您需要將最后一行替換為以下內容:
const myPhoto: Photo { id: 4, name: 'my-photo' };
const extendedPhotos = extendWith<Photo>(photos, myPhoto);
這樣 TypeScript 就可以使用來自輸入值的資訊來推斷輸出值。
uj5u.com熱心網友回復:
這可以做到(盡管這個例子需要擴展):
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]。AskeyOf是 的屬性的并集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,打字稿將推斷出obj比更具體的型別Record<Index, ObjectWithIdIndex>。其結果是:
- 我們可以使用這種型別來推斷 的
obj值的型別,然后進行約束v。 - 我們不能再分配新屬性,
obj因為打字稿不知道是否obj仍然是可擴展的 Record 型別(例如,{ a: 1 }是 的子型別Record<string, number>,但不能為其分配新屬性)。然而,我們可以obj轉換回它更通用的型別,然后擴展它。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/410125.html
標籤:
