【前端開發】TypeScript 的型別推論、型別註記與型別斷言

更新於 發佈於 閱讀時間約 34 分鐘

上一篇文章分享了 TypeScript 的定義、前端角色定位,如果你不是很確定「TypeScript 是什麼?」、「TypeScript 作為 JavaScript 的超集,在網頁開發扮演怎麼樣的角色?」這兩個問題的答案,建議可以回到上一篇先了解一下。

這一篇主要會來聊聊 TypeScript 初學者首先會遇到的幾個基礎概念,分別是:型別推論(Type Inference)、型別註記(Type Annotation)與型別斷言(Type Assertion)。

這幾個概念非常重要,如果想要深入了解 TypeScript 人一定會在 TypeScript 的官方文件或是 Stack overflow 上不斷遇到,建議在初學時就先大致了解。

在理解這幾個概念之前,建議大家要對 JavaScript 的型別系統、自動轉型的特性有基礎了解,並且了解自動轉型及缺乏型別定義的工具,會對開發上帶來什麼樣的困擾,如此才能理解今天要討論的主題可以解決什麼樣的問題。

那麼就讓我們接續之前的討論,先來聊聊 TypeScript 的型別註記系統吧:

TypeScript 的型別註記

型別註記在 TypeScript 中最基礎的概念,它可以協助開發者針對變數、參數、回傳值進行型別的標註,當註記的變數、參數及回傳值被帶入型別有誤的資料時,TypeScript 就會給予報錯。

我們來看看最基本的型別註記在 TypeScript 中是怎麼運作的,首先是最簡單的變數型別標註:

// 標註變數為字串型別
const name: string = 'Vivian';

我們會使用一個半形冒號,加上 JavaScript 中的型別來進行註記,上方的範例是將一個名為 name 的變數標註為字串型別,如果我們在標註好型別的變數戴上錯誤的型別的話,TypeScript 就會報錯:

// Type 'string' is not assignable to type 'number'.
const name: number = 'Vivian';

在使用靜態資料的狀況下我們其實很少會標註型別,這個我們後續會說明原因,接著來看看我們如何針對函式標註型別:

// 具名函式陳述式
function getString (str: string) : string {
return str
};

// 箭頭函式
const getString = (str: string): string => str;

在具名函式陳述式(Statement)中,我們可以在參數後面一樣加上冒號,在冒號後接續你想要標注的參數型別,針對回傳值的型別則接續在參數後加上冒號,接著標註你想要回傳的值型別。

不過通常我們滿少看到函式陳述式或是函式表達式(Expression)的,一般都是看到箭頭函式(Arrow Function)比較多,因為寫起來快速且看起來較為簡潔。 初學者可能會因為箭頭函式比較簡潔所以比較難理解標註的方式,不過這其實只要夠瞭解箭頭函式的運作方式,多看幾次就會習慣了:

// JavaScript 箭頭函式
const getString = str => str;

// TypeScript 箭頭函式
const getString = (str: string): string => str;

其實標註的方式跟一般的函式差不多,在參數後面一樣加上冒號,在冒號後接續你想要標注的參數型別,針對回傳值的型別則接續在參數後加上冒號,接著標註你想要回傳的值型別。

這樣我們就了解 TypeScript 中最基礎的型別標記啦,接著我們來看看 TypeScript 另外一個基礎、同時非常重要的概念:型別推論。

TypeScript 的型別推論

前文我們有提到,在使用靜態資料的狀況下我們其實很少會標註型別,這是為什麼呢?

由於 TypeScript 是透過靜態型別系統,在 JavaScript 編譯時透過上下文來「推斷」程式碼與型別標注的關係,例如:明明就標註回傳值要回傳字串型別,但卻回傳了數字,那麼 TypeScript 就會報錯。

所以其實在使用靜態資料的狀況下,我們其實就算不特別標註 TypeScript 也可以推論出指定變數的型別,以下範例在我們把滑鼠移到變數上時,TypeScript 會跳出它推論的出對應型別:

// let personName: string
let personName = '';

假設我指給這個變數錯誤的型別的話,TypeScript 一樣會報錯:

let personName = '';
// Type 'number' is not assignable to type 'string'.
personName = 123;

因此提供預設值在 TypeScript 中就會顯得特別重要,假設我們針對這個靜態資料沒有提供預設值,註解的地方是 TypeScript 顯示的型別提示:

// let personName: any
let personName;

// let personName: any
personName = 123;

// let personName: any
personName = '123';

會發現 TypeScript 會將沒有標註的型別變數推論成 any ,any 型別在 TypeScript 的意義是「可以接受任何型別的資料」,於是乎就讓我們的程式碼從 TypeScript 變回弱型別的 JavaScript 了,完全失去我們使用這個程式語言的意義。

雖然 TypeScript 擁有自動推論型別的功能,但最好只使用在「靜態、有預設值」的變數上,不然 TypeScript 可能會將回傳值的型別推論為 any,或是出現一些預期外的型別。

問題來了?對於函式的參數與回傳值如果都不標註型別,TypeScript 可以幫我們推論嗎?答案是:否,因為 TypeScript 無法針對在沒有前後文、沒有型別標註的未知參數進行推論,因此型別就會變成 any。

因此,也有開發者認為有限度的標註可以使 TypeScript 的程式碼更加簡潔且好維護,例如:針對函式嚴格標註、限制可被使用的型別,但靜態資料、預設值則讓 TypeScript 自動推論,我個人是很認同這種做法,給大家做參考。

TypeScript 的型別斷言

在 TypeScript 有一種酷東西叫做型別斷言(Type Assertion),這個算是自己在初學 TypeScript 時 最搞不懂的東西,在被自己一些沒寫好的 TypeScript 荼毒後,才理解所謂的斷言是怎麼一回事。

雖然可以把型別定義好是最理想的狀況,但有些狀況底下,還真的會有「未知(unknwon)型別」的出現,斷言就是用來處理未知型別的狀況。

關於 unknown 型別,大家可能最普遍會搞混的是:unknown 與 any 的差異是什麼?這個觀念也很重要,建議大家在初學 TypeScript 就可以養成好習慣:

unknown 是未知,any 是任意,未知並不代表這個變數想要接受「隨便一種」型別。

講了那麼多,讓我們來看看 TypeScript 官網針對斷言的範例:

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

範例中用了 getElementById 這個方法去取得指定的 DOM 元素,不過 DOM 元素在 TypeScript 中也有屬於其對應的型別,舉例來說(此處僅列舉常用的型別):

  • HTMLElement:代表一般的 HTML 元素,如 <div><span> 等。它是最基本的 DOM 元素型別,包含了一些基本的屬性和方法。
  • HTMLInputElement:代表 <input> 元素,用於處理使用者輸入的表單元素。它擁有 HTMLInputElement 特有的屬性和方法,如 valuechecked 等。
  • HTMLSelectElement:代表 <select> 元素,用於選擇列表。它提供了相關的屬性和方法,例如 selectedIndexoptions 等。
  • HTMLTextAreaElement:代表 <textarea> 元素,用於多行文本輸入。它有自己特定的屬性和方法,如 valuerows 等。
  • HTMLButtonElement:代表 <button> 元素,用於創建按鈕。它提供了與按鈕相關的屬性和方法,如 disabledclick 等。

我們要怎麼知道透過 getElementById 這個方法去取得指定的 DOM 元素是哪一種?

面對未知的型別我們就可以在 TypeScript 中,在回傳值的後方使用 as 關鍵字,並接續你想要指定的型別,當然這裡不可以出現型別的衝突,舉例來說:

/* 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. */

const x = "hello" as number;

上方的範例中,明明是字串型別,我們卻把它斷言為數字型別,此時就會報錯,TypeScript 甚至會叫你把字串型別先轉換成 unknown 型別再進行斷言。

不過這樣的使用方式顯然不合理,要極力避免,且注意實際的回傳值要是否與斷言相吻合,所以大部分會使用斷言可能是在兩種狀況:在型別沒有被推斷且可為任意型別時:

const variable = <any> as <T>;

型別未知:

const variable = <unknown> as <T>;

由於 TypeScript 型別眾多,不免會出現像是選擇 DOM 元素時,出現未知型別的問題,除非必要,不然建議不要為了符合編譯器的提示而大量使用斷言,建議在一開始就針對回傳值好好地標註型別。

除了使用 as 關鍵字,我們也可以使用下方這種方式去斷言型別未知的變數(函式回傳值),使用大於小於的符號包住你想要斷言的型別:

const element = <T>document.getElementById("element");

實際使用起來可能會長這樣:

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

這種用大於小於的方式相對少見,因為會跟 TypeScript 中的另外一個概念「泛型」很像,希望之後有機會可以介紹到,以上就是對於型別斷言的基礎介紹啦。

小結

這篇文章主要介紹了三個 TypeScript 中很重要的觀念,分別為:型別註記、型別推論與型別斷言,雖然了解這些觀念並沒有辦法讓你完整掌握 TypeScript 這門語言,但卻能讓我們在研讀相關程式碼、文件時更加順遂。

希望大家可以透過這篇文章,更加瞭解 TypeScript 的基礎概念,我是 Vivian,我們下次見。

參考資料:

  1. TypeScript Docs - Type Inference
  2. TypeScript Docs - Type Annotation
  3. TypeScript Docs - Type Assertion
留言
avatar-img
留言分享你的想法!
avatar-img
Vivian Yeh - 跨領域轉職的軟體工程師
452會員
103內容數
為了追求可以窩在座位上、可以心無旁騖思考問題、座位可以亂七八糟沒關係、不需要到處哈腰點頭跑客戶,不用腳踩十公分、連妝都可以不用化的職場人生,文組少女毅然決然踏上RD的養成日常。
2024/05/23
與 cookie 相比,localStorage 與 sessionStorage 的機制相對單純,兩者皆是瀏覽器中的儲存空間,與 cookie 最大的不同在於:localStorage 與 ⋯⋯
Thumbnail
2024/05/23
與 cookie 相比,localStorage 與 sessionStorage 的機制相對單純,兩者皆是瀏覽器中的儲存空間,與 cookie 最大的不同在於:localStorage 與 ⋯⋯
Thumbnail
2024/05/22
在瀏覽器環境中有許多的儲存空間,想要查看這些空間的話,可以透過「chrome > Dev Tools > Application > Storage」即能進行查看。 瀏覽器內存空間的差異不僅常常被拿來被當作面試考題,在實務開發中更扮演舉足輕重的角色,今天就想透過這系列的文章深度了解這些瀏覽器內存⋯
Thumbnail
2024/05/22
在瀏覽器環境中有許多的儲存空間,想要查看這些空間的話,可以透過「chrome > Dev Tools > Application > Storage」即能進行查看。 瀏覽器內存空間的差異不僅常常被拿來被當作面試考題,在實務開發中更扮演舉足輕重的角色,今天就想透過這系列的文章深度了解這些瀏覽器內存⋯
Thumbnail
2024/02/08
在前端的開發中,除了切版與串 API 外,大部分的時間都在針對表單內容進行檢核、驗證、阻擋,一方面是讓使用者在操作頁面的過程中有良好的使用者體驗,不會因為一些例外狀況(Edge Case),例如:莫名其妙的 4xx 錯誤,導致使用者卡在某個操作流程中逃不出來,另一方面是讓傳遞到後端的資料更加正確⋯⋯
Thumbnail
2024/02/08
在前端的開發中,除了切版與串 API 外,大部分的時間都在針對表單內容進行檢核、驗證、阻擋,一方面是讓使用者在操作頁面的過程中有良好的使用者體驗,不會因為一些例外狀況(Edge Case),例如:莫名其妙的 4xx 錯誤,導致使用者卡在某個操作流程中逃不出來,另一方面是讓傳遞到後端的資料更加正確⋯⋯
Thumbnail
看更多
你可能也想看
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節是一個初級的 TypeScript 教學,主要介紹了 TypeScript 中物件導向程式設計的各種核心概念,包括類別、建構子、存取修飾子、繼承、多型、封裝、介面、抽象類別、靜態類別、列舉、委派、Lambda 表達式、泛型和反射等。每個概念都通過詳細的解釋和實例代碼來進行深入的介紹。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹TypeScript中的函數,包括其基本結構、如何呼叫函數、函數的參數以及函數的返回值等相關概念。通過本章節,讀者可以學習到如何在TypeScript中使用不同的方式來定義函數,如函數聲明、函數表達式、箭頭函數和匿名函數等。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹 TypeScript 的基本資料型別,包括內建型別、型別轉換、自訂型別、元組、集合、陣列、和字典型別。透過理解和使用這些型別,可以提高代碼的可讀性和可維護性。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
本章節旨在介紹TypeScript的基本語法,包括一般結構、程式進入點、註解以及變數的定義和賦值。這些知識將幫助讀者瞭解TypeScript的基本架構,並且可以開始使用TypeScript進行開發。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
TypeScript是一種由Microsoft開發和維護的開源編程語言。它是JavaScript的超集,主要擴展了JavaScript的語法,增加了靜態類型檢查和其他特性,使得開發大型應用程序更為方便和可靠。
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
針對 JavaScript 中的原始型別和隱性轉型進行了詳細的探討
Thumbnail
本文帶你深入探索 TypeScript 中的 satisfies 特性,能幫助你實現精確的型別推導與型別檢查。透過實際案例,展示如何使用 satisfies 提升代碼的型別安全與程式碼的整潔,是每位 TypeScript 開發者不可或缺的知識。
Thumbnail
本文帶你深入探索 TypeScript 中的 satisfies 特性,能幫助你實現精確的型別推導與型別檢查。透過實際案例,展示如何使用 satisfies 提升代碼的型別安全與程式碼的整潔,是每位 TypeScript 開發者不可或缺的知識。
Thumbnail
本文介紹 TypeScript 常遇到的混合型別,以及如何透過五種型別防禦(Type Guard)來解決。涵蓋了使用型別斷言、型別謂詞、in 運算子、typeof 運算子以及 instanceof 運算子這幾種方式。透過本文的學習,能夠更好地運用 TypeScript 進行程式碼開發。
Thumbnail
本文介紹 TypeScript 常遇到的混合型別,以及如何透過五種型別防禦(Type Guard)來解決。涵蓋了使用型別斷言、型別謂詞、in 運算子、typeof 運算子以及 instanceof 運算子這幾種方式。透過本文的學習,能夠更好地運用 TypeScript 進行程式碼開發。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News