iT邦幫忙

2024 iThome 鐵人賽

DAY 14
0
JavaScript

TypeScript 初學者也能看的學習指南系列 第 14

TypeScript 初學者也能看的學習指南 14 - Type Assertion 型別斷言

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20240923/20149362WcQX7skbGN.png

剛開始學的時候,很常把這兩個英文名稱搞混XD

  • Type Annotations(型別註釋):提供編譯器某個變數、參數的型別資訊
  • Type Assertion(型別斷言):告訴編譯器某個變數、參數應被視為開發者指定的型別

光看解釋,可能看不出兩者的差異,等等看範例會更清楚
廣義來說兩者都是在明確定義型別,但型別註釋主要用於型別的安全檢查和編譯時檢查,而型別斷言則是一種「逃逸機制」,可以在特定情況下覆蓋 TypeScript 的型別推斷

關於型別註釋,有興趣可以看這篇文章
🔗 Day12 - 型別註釋 & 型別推斷

本篇文章將一起來了解「Type Assertion 型別斷言」的語法和使用時機

型別斷言(Type Assertion)

型別斷言就很像在告訴編譯器說:「來~相信我,這邊就聽我的,我知道我自己在做什麼」

型別斷言是告訴編譯器,在編譯期間將某個值視為另一個型別。如果斷言錯誤,是有可能在運行時造成錯誤的

使用時機:當你有十足把握確定你比 TypeScript 更清楚這裡會是哪種型別時

下面這是一段會報錯的 code
這是因為 id 若今天傳入的是 number 就無法使用字串的方法 toUpperCase() 來做轉換

function printId(id: number | string) {
  console.log(id.toUpperCase());
};

這時可以透過「型別斷言」來解決

語法

「型別斷言」有以下兩種常見語法:

  1. as 語法
    延續上面的例子,使用 as 後如下
function printId(id: number | string) {
  console.log((id as string).toUpperCase());
};

原本的 id 外加了一個 (),並指定它是 string (valuable as string)

  1. <type> 角括號語法

注意:在 React <> 會和 JSX 定義元素的寫法產生衝突,所以在 React 中應使用 as 關鍵字來定義型別斷言,否則會報錯唷

function printId(id: number | string) {
  console.log((<string>id).toUpperCase());
};

需注意的地方 & 範例

以下的兩段英文敘述都是擷取自官方文件

  1. 【型別斷言在編譯時會被刪除,不進行運行時檢查】

Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion

這段是在說,在 TypeScript 中型別斷言(Type Assertions)只對編譯器有影響,用來告知編譯器這裡是什麼型別。然而,一旦 TypeScript 被編譯成 JavaScript 時,型別斷言都會被「移除」,因為 JavaScript 是動態型別語言,不支持在運行時直接進行型別的檢查

來看下方這個範例吧!
someValue 的值斷言為 string,並調用 toUpperCase() 方法
這段 code 在 TypeScript 中是不會報錯的

let someValue: any = 123;
let upperCaseValue: string = (someValue as string).toUpperCase();

編譯成 JavaScript 後如下,型別斷言都消失了!因為數字無法調用 toUpperCase() 方法 ,所以在運行(runtime) 時就會報錯

let someValue = 123; 
let upperCaseValue = someValue.toUpperCase(); // ❌

// ❌ TypeError: someValue.toUpperCase is not a function
  1. 【型別斷言只允許轉換為更具體或更廣泛的型別】

TypeScript only allows type assertions which convert to a more specific or less specific version of a type

這段主要在說,使用型別斷言時,只允許將型別轉換為更具體(more specific)或更廣泛(less specific version)的型別,不允許將型別斷言成一個完全無關的型別,例如:不能將「string」斷言成「number」)

這段在 TypeScript 中會報錯

const x = "hello" as number; 

// ❌ Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  1. DOM 取得
    假設要取得特定的 HTML DOM ,可以使用 as HTML....Element
    常見的 HTML 元素類型
  • HTMLDivElement:用於 <div>
  • HTMLInputElement:用於 <input>
  • HTMLAnchorElement:用於 <a>
  • HTMLButtonElement:用於 <button>
  • HTMLFormElement:用於 <form>
    想看更多元素類型可以查看 MDN 文件 interface 的部分
const inputEl = document.querySelector(".test") as HTMLInputElement;
const inputEl2: HTMLInputElement = document.querySelector(".test");

Const Assertion

Const Assertion 稱為「常數斷言」,語法是 as const ,位置通常會放在「最後面」
「常數斷言」,它用來修改型別推斷的結果,確保所有元素都被指派為 Literal Types,而不是其他型別,例如:string, number
用在變數宣告或表達式的型別上時,它會強制將變數或表達式的型別視為常數,是不可變的(immutable),只可讀取 (readonly)

const directions = ['up', 'down', 'left', 'right']      
const directions2 = ['up', 'down', 'left', 'right'] as const; // 常數斷言

第一個為一般的 Arrary; 第二個為其常數斷言,兩者推斷的結果如下
https://ithelp.ithome.com.tw/upload/images/20240925/20149362I6TdiiQNg4.png
directions2 被推斷為 readonly ['up', 'down', 'left', 'right'],且值是不可變的,並且每個元素的型別都是 Literal Types

這時候對一個 directions2 進行操作(如:push()),就會報錯

directions2.push('NONE');
// ❌ Property 'push' does not exist on type 'readonly ["up", "down", "left", "right"]'.

雙重斷言

語法:const a = expr(表達式) as any as T(特定型別);

這是一個雙重斷言的語法
第一個 as 將 某段表達式 斷言為 any
第二個 as 再斷言為特定的型別 T
附上實際範例如下
1.

interface User {
    id: number;
    name: string;
    email: string;
}

function fetchData(): any {
    return {
        id: 1,
        name: "Hannah",
        email: "hanforwork@example.com"
    };
}
const expr = fetchData();
const user = expr as any as User; // 雙重斷言, ✅ Pass
function handler(e: Event) {
  const element = (e as any) as HTMLElement; // 雙重斷言,✅ Pass
}

雖然使用「雙重斷言」可以解決某些煩人的型別問題,但應該被視為最後的選擇,過度依賴可能會掩蓋潛在的型別問題,降低可維護性和安全性。只有在確定無法透過其他更安全的方法解決型別問題時,才考慮使用這種方法

每天講的內容有推到 github 上喔

References


上一篇
TypeScript 初學者也能看的學習指南 13 - Literal Types 明文型別/字面量(值)型別
下一篇
TypeScript 初學者也能看的學習指南 15 - any 任意型別
系列文
TypeScript 初學者也能看的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言