【前端開發】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
為了追求可以窩在座位上、可以心無旁騖思考問題、座位可以亂七八糟沒關係、不需要到處哈腰點頭跑客戶,不用腳踩十公分、連妝都可以不用化的職場人生,文組少女毅然決然踏上RD的養成日常。
留言0
查看全部
發表第一個留言支持創作者!
在前端的開發中,除了切版與串 API 外,大部分的時間都在針對表單內容進行檢核、驗證、阻擋,一方面是讓使用者在操作頁面的過程中有良好的使用者體驗,不會因為一些例外狀況(Edge Case),例如:莫名其妙的 4xx 錯誤,導致使用者卡在某個操作流程中逃不出來,另一方面是讓傳遞到後端的資料更加正確⋯⋯
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
在前端的開發中,除了切版與串 API 外,大部分的時間都在針對表單內容進行檢核、驗證、阻擋,一方面是讓使用者在操作頁面的過程中有良好的使用者體驗,不會因為一些例外狀況(Edge Case),例如:莫名其妙的 4xx 錯誤,導致使用者卡在某個操作流程中逃不出來,另一方面是讓傳遞到後端的資料更加正確⋯⋯
在 2021 年的剛轉職成為前端工程師的時候,我在面試時滿常會被詢問到 JavaScript 中閉包的議題,當時候自己回答的滿差的,於是在 2022 年時,我寫了一系列的有關於函式程式設計鐵人賽的文章, 裡頭就有簡單提到有關於閉包的議題。
在之前的文章當中曾經提到過 JavaScript 中的物件有一個特別的機制:傳參考(Called by reference),如果正確性再高一點的話,則可以稱之為傳共享(Called by sharing)。
在先前的型別文章中,我們曾經聊過 JavaScript 常用的一些型別,但針對布林這個型別,我們沒有做太多的解釋,原因在於布林值在 JavaScript 會有一個特殊的規則:自動轉型 。 自動轉型可說是讓 JavaScript 為弱型別、且難以管理的最重要的要素,接著就來讓我們來聊聊什麼是自動轉型
在剛開始寫 JavaScript 可能大多數的人不會特別意識到 JavaScript 的型別系統有什麼特別之處,我是在看完 Youtube 上 CS50 的課程,才理解到在不同的程式語言中,會因為語言的特性而有不同的系統,JavaScript 就是偏向比較特別的那一種。
在前端開發中,很常會有需要轉址的需求,且處理的手法滿因人而異的,所以今天就想要來整理一些常見的 JavaScript 頁面轉址方式,以及各自的差異。
你可能也想看
Google News 追蹤
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
前言 現在的前端需求已經越來越高,要考慮HTML及CSS的切版美觀程度,以及React以及Flutter所提出的元件(Componet、widget)觀念,也就是將元件模組化,使元件可以更動態的被程式運行,而不用靜態的客製化每一個介面。開發一個好的元件可以提升整體的開發速度,讓任何使用元件的開發者
Thumbnail
前端開發者常會遇到需要網頁素材的情況,雖然在公司中都可能有可以配合的平面設計師或是UIUX設計師,但在這個多工高效的時代不免也需要前端開發者也可以處理簡單的設計,也可提升設計審美或與設計師溝通的能力。 然而前端開發者也算是擁有設計師的天賦,透過程式碼來完成平面設計,將網頁的每個介面都視為平面設計,
Thumbnail
C2 開始終於使用到 visual studio code, 這個階段學到 JavaScript 核心概念、DOM 操作、API 串接、MVC 架構, 每天都在 coding,每週都在追進度, 用壓縮到極致的時間寫完作業, 但跟著課程內容寫程式碼,真的有學到嗎? 有,吧。 畢竟在寫最後的電影清單
Thumbnail
今天想透過這篇文章與各位分享如何透過 Chrome Devtool 的 Performance Tab 來檢測網頁在執行時的各種效能指標,讓網頁的 Runtime Performance 不再成為你 debug 時的瓶頸!
本篇最主要是要了解網站前端工程師究竟是什麼?主要工作內容又有哪些?將從三個部份來說明:前端的工作內容、前端所需的軟實力以及前端所需的硬實力。
Thumbnail
如果有接觸過網頁開發的讀者,或者會聽過很多不同的專有名詞,其中不少得Front-end (前端) 和 Back-end (後端)這兩個字詞。有一些網站可能只需要設計師和前端開發者,有些則需要後端開發者和測試人員等等,那究竟前端和後端如何分辨?首先,讓我們搞清楚前端開發是什麼一回事。
Thumbnail
接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
Thumbnail
🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
Thumbnail
前言 現在的前端需求已經越來越高,要考慮HTML及CSS的切版美觀程度,以及React以及Flutter所提出的元件(Componet、widget)觀念,也就是將元件模組化,使元件可以更動態的被程式運行,而不用靜態的客製化每一個介面。開發一個好的元件可以提升整體的開發速度,讓任何使用元件的開發者
Thumbnail
前端開發者常會遇到需要網頁素材的情況,雖然在公司中都可能有可以配合的平面設計師或是UIUX設計師,但在這個多工高效的時代不免也需要前端開發者也可以處理簡單的設計,也可提升設計審美或與設計師溝通的能力。 然而前端開發者也算是擁有設計師的天賦,透過程式碼來完成平面設計,將網頁的每個介面都視為平面設計,
Thumbnail
C2 開始終於使用到 visual studio code, 這個階段學到 JavaScript 核心概念、DOM 操作、API 串接、MVC 架構, 每天都在 coding,每週都在追進度, 用壓縮到極致的時間寫完作業, 但跟著課程內容寫程式碼,真的有學到嗎? 有,吧。 畢竟在寫最後的電影清單
Thumbnail
今天想透過這篇文章與各位分享如何透過 Chrome Devtool 的 Performance Tab 來檢測網頁在執行時的各種效能指標,讓網頁的 Runtime Performance 不再成為你 debug 時的瓶頸!
本篇最主要是要了解網站前端工程師究竟是什麼?主要工作內容又有哪些?將從三個部份來說明:前端的工作內容、前端所需的軟實力以及前端所需的硬實力。
Thumbnail
如果有接觸過網頁開發的讀者,或者會聽過很多不同的專有名詞,其中不少得Front-end (前端) 和 Back-end (後端)這兩個字詞。有一些網站可能只需要設計師和前端開發者,有些則需要後端開發者和測試人員等等,那究竟前端和後端如何分辨?首先,讓我們搞清楚前端開發是什麼一回事。