網站搜尋

javascript - 為什麼 TypeScript 的 `never` 類型對條件物件需要問號?


這段程式碼可以正常運作,用來有條件地要求 isDirty 欄位成為 lecture 物件的一部分:

  • 如果 id 的類型是 string,我必須添加一個 isDirty 欄位:
  • 如果 id 的類型是 number,我無法向該對象添加 isDirty 欄位。
type LectureT = LectureIdT & {
  title: string;
};

/**
 * Lecture must have an `isDirty` field if `id` is of type string, but should have no `isDirty` field if `id` if of type number.
 */
type LectureIdT =
  | { id: string; isDirty: boolean }
  | { id: number; isDirty?: never };
  

const lectureMockWithNumberId: LectureT = {
  id: 1,
  title: 'Lecture 1',
  // isDirty: false, // Uncomment to see error
};

const lectureMockWithStringId: LectureT = {
  id: "1",
  title: 'Lecture 2',
  isDirty: false,
};

這段程式碼可以在這個 TS Playground 中進行嘗試。

問題:為什麼我在 isDirty?: never 中需要問號?


我對此事的調查:

當我從 isDirty?: never 移除問號以得到 isDirty: never 時,它就不再工作了:

type LectureIdT =
  | { id: string; isDirty: boolean }
  | { id: number; isDirty: never };

lectureMockWithNumberId 突然出現 TypeScript 錯誤:

Type '{ id: number; title: string; }' is not assignable to type 'LectureT'.
  Type '{ id: number; title: string; }' is not assignable to type '{ id: number; isDirty: never; } & { title: string; }'.
    Property 'isDirty' is missing in type '{ id: number; title: string; }' but required in type '{ id: number; isDirty: never; }'.

我們在最後一句話中看到,它說 isDirty 是必需的。但我覺得這似乎不對!它的類型是 never -> 所以它應該 永遠 不會是必需的,不是嗎?

為什麼我必須加上問號才能使它工作。這似乎不合邏輯。

為了試圖理解它,我將其改為 isDirty: never | undefined:

type LectureIdT =
  | { id: string; isDirty: boolean }
  | { id: number; isDirty: never | undefined };

但令我驚訝的是,錯誤仍然存在!例如,a?: string 中的 ? 不就是等同於 a: string | undefined 嗎?

我不明白……

  1. ...為什麼這裡一開始就需要問號 ?,而 isDirty: never; 基本上是不可能的。
  2. ...為什麼 ? 在與 never 一起使用時會有不同的表現?

答案#1

讓我一個一個地回答這些問題:

我們在最後一句看到,它說 isDirty 是必填的。但這對我來說似乎不對!它的類型是 never -> 所以它應該永遠不會是必填的,不是嗎?

never 類型在與可選物件屬性互動方面沒有任何特殊之處。你可以將 never 視為與其他類型(string、number 等)相同。不同之處在於,與這些其他類型不同,沒有任何值可以賦給 never。你可以說,never 類型的變數「永遠」無法被賦予有效值。

但令我驚訝的是,錯誤仍然存在!例如,a?: string 中的 ? 不等於 a: string | undefined 嗎?

不。考慮這兩個物件之間的差異:{} 和 {a: undefined}。在一種情況下,鍵完全不存在。另一種情況下,它存在並且值為 undefined。前者物件可以賦值給 {a?: string},但不能賦值給 {a: string | undefined}。

這導致:

為什麼這裡一開始就需要問號 ?,而 isDirty: never; 基本上是不可能的。

如同上述範例,如果你在鍵名稱上不加上 ?,那麼該鍵在類型中就是 必填的。這個鍵就是 必須存在的。但該鍵的值的類型是 never。因此,你無法為該鍵賦予任何有效的值。

所以簡而言之,確實沒有問號是不可能的,因為這是對需求的矛盾:那個鍵必須存在,但沒有值是有效的。

最後:

為什麼 ? 在與 never 一起使用時會有不同的表現。

這僅僅是 never 的通常屬性與 ? 的通常行為結合的結果。這兩個特定特性的相互作用沒有什麼特別之處。

以 {isDirty?: never} 為例,這表示以下情況之一必須為真:

  • 該物件不包括 isDirty 鍵。
  • 該物件確實包含 isDirty 鍵,並且它的值類型是 never。

由於這兩個情況中的第二個是不可能的(根據上述論點),那麼只剩下第一個情況:符合這種類型的物件不得包含 isDirty 鍵。

答案#2

`never` 也被稱為空類型——在 `never` 類型的集合中沒有任何值存在。沒有值可以被賦給 `never` 類型的變數。即使你寫 `function f(): never`——函數的返回類型並不是 `never`——只是用來指定,該函數 `never` 返回(拋出異常或陷入無限循環)。

除了「函數不返回」之外,我不知道 never 的其他使用情況。