# TypeScript - 型別推論與聯合型別 ## 型別推論 Type Inference 變數在宣告的時候如果沒有明確指定任何型別,TypeScript 就會依照型別推論(Type Inference)的規則來推測出變數的型別。 ### 變數的型別推論 ```typescript let something = 'Hello' // 等同於 let something: string = 'Hello' something = 7 // index.ts: error TS2322: Type 'number' is not assignable to type 'string' ``` 因為變數 `something` 的初始值為字串,TypeScript 編譯器便會推斷其型別為 `string`。如果初始值是數字、布林值⋯⋯等其他型別,編譯器也會推斷出相應的型別。 因此 `let something = 'Hello'` 事實上等同於 `let something: string = 'Hello'`。 ### 函式參數和返還值型別推論 ```typescript function add(a, b) { return a + b } // 等同於 function add(a: number, b: number): number { return a + b } ``` ### 未宣告型別與初始值的變數型別推論 如[上一篇所述](https://hackmd.io/C5c4PdVrSGuiaHEqxhsOKw?both#%E6%9C%AA%E5%AE%A3%E5%91%8A%E5%9E%8B%E5%88%A5%E7%9A%84%E8%AE%8A%E6%95%B8),如果變數在宣告的時候既**沒有指定任何型別也沒有給予初始值**,TypeScript 便無法推斷出變數的型別,因而會將變數標示為 `any` 型別。 ```typescript let something // 等同於 let something: any ``` 而當一個變數的型別為 `any` 時: - 可以將任何型別的值賦予給它(不管之後有沒有賦值都仍會是 `any` 型別) - 並且對它的任何操作所返回的值的型別也都會是 `any` 這樣等於是門戶大開、完全跳過了 TypeScript 的型別檢查。 ## 聯合型別 Union Types TypeScript 中的一種進階型別,用來表示允許變數可以是多個不同型別。 聯合型別使用垂直線 `|` 來分隔不同的型別。 ```typescript let value: string | number // value 可以是 string 或 number value = 'seven' // 正確, value 可以是 string value = 7 // 正確,value 可以是 number value = true // 錯誤,value 不能是 boolean ``` 這裡的 `someValue` 只能是 `string` 或是 `number`,不能是其他型別。 ### 為什麼會需要聯合型別 聯合型別在 TypeScript 中的作用是允許一個變數具有多種不同的資料型別,這可以讓我們更靈活地處理變數可能具有多種不同型別的狀況,像是多樣化的輸入或資料。 假設我們正在開發一個商品訂單系統,其中一個功能是要顯示商品的資訊。而每個商品都有其獨特的特性。有些商品可能是數字,代表數量;有些則是文字,代表商品名稱。 這時候,你可以使用聯合型別來處理這些可能的資料型別。 ```typescript function displayProductInfo(product: number | string) { if (typeof product === "number") { // 如果輸入的是數字(例如 3),則代表著商品數量 console.log(`Quantity: ${product}`); } else { // 如果輸入的是字串(例如 "Apple"),則代表著商品名稱 console.log(`Product Name: ${product}`); } } displayProductInfo(3); // 輸出:Quantity: 3 displayProductInfo("Apple"); // 輸出:Product Name: Apple ``` 這個例子中,`product` 是一個聯合型別,可以是 `number` 或是 `string`。 當我們呼叫 `displayProductInfo` 函式時,它會根據輸入的資料的型別顯示對應的商品資訊。這樣我們就可以同時處理不同型別的商品資料,而不需要為每種型別寫不同的函式。 ### 聯合型別的常用情境 聯合型別在以下的情境下很有用: 1. **處理不確定的資料型別**:當我們在處理用戶輸入、 API 回應等資料時,這些型別的資料可能是不確定的。使用聯合型別可以容納多種可能的型別,避免錯誤。 2. **提供多種選項**:有時候你想讓一個變數可以存放多種選項,比如一個函式的參數可以是數字或字串。 3. **處理不同型別的集合**:在集合中,比如陣列或物件,可能包含不同型別的元素。使用聯合型別可以確保集合中的元素可以是其中之一。 ### 存取聯合型別的屬性和方法 當 TypeScript 不確定一個聯合型別的變數到底是哪個型別的時候,只能存取此聯合型別的所有型別裡**共有**的屬性或方法: ```typescript function getLength(something: string | number): number { return something.length } // index.ts:2:22 - error TS2339: Property 'length' does not exist on type 'string | number'. // Property 'length' does not exist on type 'number'. ``` 上述例子中,因為 `length` 不是 `string` 與 `number` 的共同屬性,因此會報錯。 如果是要存取 `string` 和 `number` 的「共同屬性」是沒問題的: ```typescript function getLength(something: string | number): number { return something.toString().length } ``` 上述例子中,先用 `string` 和 `number` 兩種型別共有的 `toString()` 方法將輸入轉為字串再存取其 `length` 屬性。 ### 聯合型別賦值與型別推論 聯合型別的變數在被賦值的時候,會根據型別推論的規則推斷出該變數的型別。 ```typescript let mixedValue = string | number mixedValue = 'hello' // TypeScript 推斷為 string 型別 console.log(mixedValue.length) // 5 mixedValue = 7 // TypeScript 推斷為 number 型別 console.log(mixedValue.length) // 會報錯 // error TS2339: Property 'length' does not exist on type 'number'. ``` 當我們將字串 `'hello'` 賦值給 `mixedValue` 變數時, TypeScript 會根據型別推論來推斷 `mixedValue` 為 `string` 型別。此時,存取它的 `length` 屬性不會報錯。 當我們再次賦值數字 7 給 `mixedValue` 變數,此時的 `mixedValue` 被推斷成了 `number` 型別。而 `length` 屬性並不存在於 `number` 型別中,因此會報錯。 ## Ref - [TypeScript 新手指南 (gitbook.io)](https://willh.gitbook.io/typescript-tutorial/) - [從零開始學 TypeScript 計畫](http://anna-yufeng.com/)