Skip to main content

Command Palette

Search for a command to run...

TypeScript - 型別斷言(Type Assertion)

Published
•2 min read•View as Markdown

型別斷言是 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} 時,編譯器可以提供其屬性及方法的提示。

處理非空斷言

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

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

More from this blog

JavaScript - Scope 作用域 & Closure 閉包

Scope 作用域 留言 什麼是作用域 「作用域就是一個變數的生存範圍,一旦出了這個範圍,就無法存取到這個變數」。 當我們把變數 a 宣告在 function 中,function 之外的地方都無法取用這個變數: // 把變數宣告在 function 中 function test1() { var a = "hello" } console.log(a) // Uncaught ReferenceError: a is not defined 但當我們把變數 b 宣告在全域,fu...

Oct 3, 20236 min read
JavaScript - Scope 作用域 & Closure 閉包

JavaScript - this 是誰、指向哪裡,以及 call、apply、bind

this 是什麼 this 是 JavaScript 的一個關鍵字 this 是 function 執行時,自動生成的一個內部物件 隨著 function 執行場合的不同,this 所指向的值,也會有所不同 this 與 function 在何處被宣告完全無關,而是取決於 function 被呼叫的方式 在大多數的情況下, this 代表的就是呼叫 function 的物件 (owner Object of the function) 當 function 是某個 object 的 ...

Oct 3, 20233 min read
JavaScript - this 是誰、指向哪裡,以及 call、apply、bind

JavaScript - Hoisting 提升

Hoisting 是怎麼發生的? 變數和函數的宣告會在編譯階段就被放入記憶體,但實際位置和程式碼中完全一樣。 從這段 MDN 對於 hoisting 的說明大概可以了解到,Javascript 在執行程式碼之前會先進行編譯,而在編譯的過程中會將變數宣告以及函式宣告提升 (hoist) 到該 scope 的頂端,但需注意這邊並非實際改動程式碼的位置。 JS 在運作時是分成「編譯」和「執行」兩個步驟。而 hoisting 是發生在編譯的階段。 JS 在編譯的階段會將變數及函式的宣告處理好(h...

Oct 3, 20233 min read
JavaScript - Hoisting 提升

Yachu's Dev Memo

18 posts