網站搜尋

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 連結