TypeScript - 型別斷言(Type Assertion)

·

2 min read

型別斷言是 TypScript 的一種機制,允許我們手動指定一個明確的型別,提供 TypeScript 編譯器無法自行推斷的額外型別資訊。

我們可以透過這兩種寫法來指定型別斷言:

語法

尖括號 <>(angle-bracket)語法

語法:<type>value - 在需要斷言的變數前加上 <Type> 即可

 const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas")

as 語法

語法:value as type

 const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement

需注意, tsx 語法(React jsx 語法的 ts 版)中必須使用 as 寫法。

由於尖括號 <> 在 JSX 語法中表示一個標籤的開始,TypeScript 無法分辨尖括號是用於 JSX 元素還是型別斷言亦或是泛型,因此在 tsx 文件中需使用 as 寫法來避免這種衝突。

型別斷言的好處

當我們在處理一些能夠知道比 TypeScript 型別推斷還要多的型別資訊時,型別斷言可以讓我們更準確地指定型別,確保程式碼的可讀性、可靠性和可維護性。

  • 提供更多型別信息:當你有關於某個值的確切型別資訊時,型別斷言可以提供這些資訊給 TypeScript 編譯器,使型別檢查更準確。

  • 處理特定情況:在特定情況下, TypeScript 的型別推斷可能會無法正確辨別型別。當我們確定這些情況下的型別是正確的,使用型別斷言可以確保程式碼在這些情況下能夠正確地運作。

  • 靈活性:型別斷言讓我們在特定情境下可以不必遵循 TypeScript 嚴格的型別規則。這在處理一些不規則或特殊的情況時很有用。

型別斷言的常見情境

處理 DOM 元素

當我們從 DOM 中獲取元素時,通常 TypeScript 只知道它是 HTMLElement 或其子類型,但我們可能更確定它的具體型別。這時就可以使用型別斷言來更準確地指定型別:

const element = document.getElementById("myElement") as HTMLInputElement;
element.value = "Hello, TypeScript!";

在這個例子中,我們使用 document.getElementById 來選取頁面上的元素,TypeScript 只知道會回傳某種 HTMLElement,但並不知道是哪一種。

如果我們知道選取的會是哪一種類型的 HTMLElement,就可以更明確地指定它可能是 HTMLInputElement 類型的元素。

處理任意值 any 型別

當我們在處理一個 any 型別的值時,可能需要將其斷言為更具體的型別。這樣可以避免在後續的程式碼中使用額外的型別檢查和警告。

let value: any = "This is a string.";

// 沒有使用型別斷言
let stringLength1: number = value.length; // 編譯器不會報錯,但無法確保 `value` 真的是字串

// 使用型別斷言
let stringLength2: number = (value as string).length; // 確保編譯器將 `value` 視為字串,並且能夠取得其長度

處理聯合型別

使用聯合型別時,當 TypeScript 無法確定一個聯合型別的變數到底是哪個型別的時候,我們只能取用此聯合型別的所有型別裡共有的屬性或方法

但有時候我們會需要在還不確定型別的時候就訪問其中一個型別的屬性或方法。此時就可以使用型別斷言,將想要訪問的變數斷言成指定的型別:

// 沒有使用型別斷言
function getLength(something: string | number): number {
    if (something.length) {
        return something.length;
    } else {
        return something.toString().length;
    }
}
// 錯誤:Property 'length' does not exist on type 'string | number'.
// 錯誤:Property 'length' does not exist on type 'number'.

// 使用型別斷言
function getLength(something: string | number): number {
    if ((<string>something).length) {
        return (<string>something).length;
    } else {
        return something.toString().length;
    }
}

在上面沒有使用型別斷言的例子中,因為 something 的型別有可能是 string 也有可能是 number 因此在取用 length 屬性時會報錯( number 沒有 length 屬性)。

我們透過型別斷言 <string>something 將變數 something 斷言為 string 型別。取用 length 屬性是沒問題的。

處理不同 API 返回的數據

當我們們使用外部 API 或第三方套件時,返回的數據可能會包含不同的型別。此時,我們可以使用型別斷言來確保程式碼使用正確的型別。

interface ApiResponse {
  status: "success" | "error";
  data?: any;
  message?: string;
}

function handleApiResponse(response: ApiResponse) {
  if (response.status === "success") {
    // 沒有使用型別斷言
    let data1 = response.data // `data1` 的型別為 `any`,編譯器無法提供有關其屬性或方法的提示
    console.log(data1)

    // 使用型別斷言
    let data2 = response.data as { name: string, age: number } // 確保編譯器將 `response.data` 視為具有 `name` 和 `age` 屬性的物件
    console.log(data1)
  } else {
    console.error(response.message);
  }
}

使用型別斷言 response.data as {name: string, age: number} 時,編譯器可以提供其屬性及方法的提示。

處理非空斷言

確定一個變數一定不會是 nullundefined 時,可以使用非空斷言 !

const element = document.getElementById("myElement")!;
element.innerText = "Hello, TypeScript!";

使用型別斷言的注意事項

  1. 不允許斷言成一個聯合型別中不存在的型別:型別斷言並非型別轉換,要將變數斷言成不存在聯合型別中的型別是不允許的
function toBoolean(something: string | number): boolean {
    return <boolean>something;
}

// 錯誤: Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
// 錯誤:Type 'number' is not comparable to type 'boolean'.
  1. 使用型別斷言會跳過 TypeScript 的型別檢查:型別斷言會使 TypeScript 無法進行部分或完整的型別檢查。如果斷言不正確,可能會導致意外的行為或錯誤。

在使用型別斷言時,必須確保有足夠的理由和型別資訊來進行斷言,否則可能會造成意料之外的錯誤。此外,也應該在適當的時候選擇使用更強大的 TypeScript 功能,例如型別定義和聯合型別。

Ref