為什麼 TypeScript 會對具有陣列類型聯合的陣列項推斷出這種類型?
我在理解為什麼當陣列元素的類型是聯合類型且類型「重疊」時,TypeScript 會推斷某個類型時遇到困難。我已經將問題簡化到這個最小重現範例:
interface Base {
id: string;
}
interface Child {
id: string;
parentId: string;
}
interface Obj {
nodes1: Base[] | Child[];
nodes2: (Base | Child)[];
}
const obj: Obj = {
nodes1: [],
nodes2: []
};
const node1 = obj.nodes1[0]; // typed as Base
const node2 = obj.nodes2[0]; // typed as Base | Child
如果我更改 Base,使它不僅僅像這樣是 Child 屬性的子集:
interface Base {
baseId: string;
}
現在,node1 和 node2 都被推斷為 Base | Child。這正是我第一次所預期的。在我的實際程式碼中,這個陣列可以全部是 Base 類型,或者全部是 Child 類型,所以類型感覺更好應該是 Base[] | Child[],但目前我不得不使用 (Base | Child)[]。我可以進行更大的重構來引入泛型,但這不是一個簡單的改動。
為什麼類型只被推斷為 Base,而不是 Base | Child?
答案#1
請參見 microsoft/TypeScript#43667 以獲得權威答案。這是 TypeScript 的設計限制。
如你所知:在 TypeScript 的結構型別系統中,Child 是 Base 的子型別,即使它並未明確宣告如此。因此,每個 Child 類型的值 也是 Base 類型的值(雖然反之不然)。這意味著 Child | Base 等同於 Base……儘管編譯器並不總是積極地將前者「簡化」為後者。(可將此與 "foo" | string 的行為相比較,後者會被編譯器立即簡化為 string。)
子類型縮減通常是可取的,但在某些情況下,Child | Base 的行為與 Base 的行為在觀察上是不同的,例如多餘的屬性檢查、IntelliSense 提示,或使用 in 運算符時發生的那種類型不安全的保護。你並沒有說明為什麼對你來說獲得 Base 而不是 Child | Base 很重要,但大概是這些可觀察的差異其中之一或類似的情況。
我在這裡的建議首先是要仔細考慮你是否真的需要這種區分。如果需要,那麼你可以考慮防止 Base 成為 Child 的子類型,可能的方法是給它添加一個可選屬性:
interface Base {
id: string;
__baseMarker?: never; // <-- added property
}
所以現在我們說一個 Base 有一個 string 類型的 id,並且要麼沒有 __baseMarker 屬性,要麼有一個值類型為 never(這是不可能的)。所以基本上就是「沒有 __baseMarker 屬性」(嗯,它可以是 undefined)。這與你原來的定義並沒有太大區別,但現在 Child extends Base 為假,且 Child | Base 不等同於 Base:
const node1 = obj.nodes1[0]; // typed as Base | Child
程式碼的 Playground 連結
