Typescript 抱怨 'T' 可能會用 'MyType' 的不同子類型來實例化
我正在試著理解這個錯誤,但我似乎無法弄清楚它的意思……我正在使用一些對我來說相當新的 TypeScript 功能(條件類型),所以我可能只是誤解了它們的運作方式。
任何幫助都將不勝感激!🙌
這是我的程式碼:
type PrimaryCell = "primary" | "header";
type BorderCell = "top" | "bottom";
type AnyCell = PrimaryCell | BorderCell;
type Cell<T extends AnyCell, U extends string | string[]> = {
content: U;
type: T;
};
type TallCell<T extends PrimaryCell> = Cell<T, string[]>;
type ShortCell<T extends AnyCell> = Cell<T, string>;
// This is a conditional type for determining which cell types are allowed given a cell type
type TallOrShort<T extends AnyCell> = T extends PrimaryCell
? TallCell<T>[] | ShortCell<T>[]
: ShortCell<T>[];
class RegularRow<T extends PrimaryCell, U extends TallOrShort<T>> {
cells: U;
type: T;
constructor(cells: U) {
this.cells = cells;
this.type = this.cells[0].type; // <-- ERROR
}
}
這是錯誤:
Type '"primary" | "header"' is not assignable to type 'T'.
'"primary" | "header"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'PrimaryCell'.
Type '"primary"' is not assignable to type 'T'.
'"primary"' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'PrimaryCell'.ts(2322)
這是我的臨時解決方法(不理想)
this.type = this.cells[0].type as T;
如果有更好的方式來實現這個,我很樂意聽取建議。
我的目標是使用 Typescript 來強制 RegularRow 類別應該有一個儲存格陣列,這些儲存格可以是 Tall 或 Short(但不能同時是兩者),而儲存格類型 "primary"、"header"、"top" 或 "bottom" 應決定該儲存格可以是 Tall 還是 Short。這就是 TallOrShort 條件型別發揮作用的地方。
我通常不會深入研究這麼多型別,但我正在製作一個函式庫,覺得這是一個學習更進階 TypeScript 功能的好機會。
提前感謝你!
我嘗試過的事情
我已經嘗試了各種方法來解決它。看起來這種類型造成了一些問題。
type TallOrShort<T extends AnyCell> = T extends PrimaryCell
? TallCell<T>[] | ShortCell<T>[]
: ShortCell<T>[];
將 TallCell<T>[] 改成 TallCell<T> 或幾乎任何其他東西都可以讓錯誤消失。不確定為什麼會這樣。
答案#1
…的問題
type TallOrShort<T extends AnyCell> = T extends PrimaryCell
? TallCell<T>[] | ShortCell<T>[]
: ShortCell<T>[];
RegularRow<T, U> 的實作內部,T 和 U 是泛型,而 RegularRow<T, U> 是條件型別。TypeScript 傾向於 延遲 泛型條件型別的評估。它無法簡單地評估型別,因為它還不知道 T 是什麼。此外,它也不會嘗試 分析 型別來決定泛型條件型別,因為通常從這些型別中提取有用的不變性是困難的;泛型條件型別可以計算出許多複雜的型別函式。 當你寫 TallOrShort<T> 時,你 打算 表達的是,不論 T 是什麼,TallOrShort<T> 都會是一個 Call<T, any> 的陣列,所以 type 是型別 T。但那個資訊並沒有明確寫在定義中。你需要分析型別才能弄清楚這點。TypeScript 無法做到這件事。
我在這裡的建議是重寫 TallOrShort<T>,使其 直接 編碼這些資訊。例如:
type TallOrShort<T extends AnyCell> =
Cell<T, string | (T extends PrimaryCell ? string[] : never)>[]
裡面仍然有一種類型條件,但它被推到了一個內部作用域。TallOrShort<T> 是 Cell<T, ???>[],這就是你的程式碼能編譯所需的一切:
class RegularRow<T extends PrimaryCell, U extends TallOrShort<T>> {
cells: U;
type: T;
constructor(cells: U) {
this.cells = cells;
this.type = this.cells[0].type; // okay
// ^?(property) type: T extends PrimaryCell
}
}
U 參數傳給 Cell 是 string | (T extends PrimaryCell ? string[] : never);如果 T 是 PrimaryCell,那麼你會得到 Cell<T, string | string[]>,而如果 T 不是 PrimaryCell,那麼你會得到 Cell<T, string>。這類似於 TalCell<T>[] | ShortCell<T>[] 與 ShortCell<T>[](但不完全相同;一個是聯合型別的陣列,另一個是陣列的聯合型別,但希望這個區別在這裡不會太重要)。
在某種程度上,如果條件類型是通用的,對編譯器來說總是很難分析,但如果我們能將類型限制在內部範圍內,那麼編譯器可以在放棄之前評估更多的類型。
程式碼的 Playground 連結
