--- GA: G-7BSJTG7RYN --- `never`:各種型別的 subtype。假設型別註記為 `boolean` ,還是有可能被型別推論為 `never` ,表示這個變數或函式有一些條件狀況可能未被處理。 `any`:通常只在導入 TS 初步除錯時使用,開發專案時 **盡量別!使!用!** `unknown`:似 `any` ,但 `unknown` 型別的變數值,只能指派給 `any` 或 `unknown` 型別的變數。 -- ### Never 通常有兩種情況會使用此型別: 1. 無法跳脫函式 2. 必須中斷程式執行的狀況 沒辦法跳脫函式的狀況,最直觀就是函式內發生了無窮迴圈: ```typescript= const loopForever = () => { while(true) { // 程式會卡在這無法跳脫 } } ``` 不過通常很難第一時間就發現,程式因某個條件而導致的無窮迴圈。看到這裡,有些人可能會以為是 TS 檢測時進行 `型別推論` 才會需要 `never` 型別來警告。 其實不然,再看一個會造成 `never` 型別的範例: ```typescript= function checkData(x: string | number): boolean { if (typeof x === 'string') { return true; } else if (typeof x === 'number') { return false; } } ``` 在嚴格模式下,會造成某些條件(`else`)沒有返回值,**這種函式**就會被推論為 `boolean` 下的 `never` 型別。 > **提醒**:`never` 是各種型別的 subtype。 如果傳值符合條件,回傳了 `true` (`boolean` ),就與型別推論結果(`never`)產生衝突,導致 TS 報錯,因此,比較常見的作法,我們會在最後 `return` 一個 `never` 型別的東西。 那麼,我們試著在函式最後,再加上一段語法,針對不符合任何條件的結果,拋出一個錯誤訊息。 事實上這個動作其實很常見,尤其是在 `try...catch` 的語法中。因此,我們通常將它寫成 `function`: ```typescript= function throwError(msg: string): never { throw new Error(msg); } ``` 回憶一下 [<Day02:型別系統 - 原始型別 Primitive Types>](https://hackmd.io/@elzuoc/B1zeGU61n#TypeScript-%E5%A2%9E%E5%8A%A0%E7%9A%84%E5%9E%8B%E5%88%A5) 我們有說明過的 `void` 。 你會發現這裡並不是使用 `void` 作為型別註記,因為 `throw new Error` 還是算一個回傳值。 所以如果我們這麼做: ```typescript= function checkData(x: string | number): boolean { if (typeof x === 'string') { return true; } else if (typeof x === 'number') { return false; } return throwError('Error message'); } ``` 這時候就會由於回傳的 `throwError()` 是 `never` 型別,與 `checkData()` 型別推論一致,最終通過 TS 的型別檢測。 -- ### Any 在既有專案導入 TypeScript 時,會因 JS 本身沒有型別註記,而出現預期性錯誤,在正式開發前,我們會習慣先將所有的型別註記為 `any`,這可以**讓 TS 跳過型別檢測**,讓專案可以先正常運作。 > **注意:`any` 會讓 TS 跳過型別檢測!** 這代表註記 `any` 只是為了讓整個格式符合 TS 的撰寫方式,它還是會跟 JS 一樣,容易引發非預期的錯誤。 所以導入與修正型別,並確定程式可運作後,請 **盡可能** 依序將每個 `any` 型別重新註記為適當的型別。 既然說「盡可能」,當然就會有一些例外。 實務上,確實會有型別無法確定的狀況,像是 `JSON.parse` ,每一個屬性都有不同的型別,除非 `屬性` ,`值` 都是確定的,可註記為 `object`,除此之外也只能用 `any`。 -- ### Unknown 有兩個重點需要瞭解: 1. 變數指派限制 2. `unknown` 型別的變數特性 #### 變數指派限制 與 `any` 型別類似,兩者相同之處在於,變數可以接收任何型別的值: ```typescript= let anyType: any; anyType = 123; anyType = 'String'; anyType = true; anyType = null; anyType = undefined; anyType = { say: 'Hello TS' }; anyType = [1, true, null, 'String']; anyType = function() { console.log('Hello TS') }; anyType = new Object(); ``` ```typescript= let unknownType: unknown; unknownType = 123; unknownType = 'String'; unknownType = true; unknownType = null; unknownType = undefined; unknownType = { say: 'Hello TS' }; unknownType = [1, true, null, 'String']; unknownType = function() { console.log('Hello TS') }; unknownType = new Object(); ``` 而相異處則是 > `any` 型別的變數值,可以指派給任何型別的變數 > `unknown` 型別的變數值,只能指派給 `any`, `unknown` 型別的**變數** 將 `any` 型別變數值,指派給任何型別的變數: ```typescript= let anyType: any; let setNumber: number; let setString: string; let setBoolean: boolean; let setNull: null; let setUndefined: undefined; let setObjectLiteral: { name: string, age: number }; let setArray: number[]; let setFunction: () => void; let setObject: Object; let setAny: any; let setUnknown: unknown; setNumber = anyType; // Pass setString = anyType; // Pass setBoolean = anyType; // Pass setNull = anyType; // Pass setUndefined = anyType; // Pass setObjectLiteral = anyType; // Pass setArray = anyType; // Pass setFunction = anyType; // Pass setObject = anyType; // Pass setAny = anyType; // Pass setUnknown = anyType; // Pass ``` 將 `unknown` 型別變數值,指派給任何型別的變數: ```typescript= let unknownType: unknown; let setNumber: number; let setString: string; let setBoolean: boolean; let setNull: null; let setUndefined: undefined; let setObjectLiteral: { name: string, age: number }; let setArray: number[]; let setFunction: () => void; let setObject: Object; let setAny: any; let setUnknown: unknown; setNumber = unknownType; // Error setString = unknownType; // Error setBoolean = unknownType; // Error setNull = unknownType; // Error setUndefined = unknownType; // Error setObjectLiteral = unknownType; // Error setArray = unknownType; // Error setFunction = unknownType; // Error setObject = unknownType; // Error setAny = unknownType; // Pass setUnknown = unknownType; // Pass ``` -- #### `unknown` 型別的變數特性 假設有個變數 `setSomething`,我們將某個 `unknown` 型別的變數**值**指派給 `setSomething`。 ```typescript= setSomething = unknownType; ``` 這時, > 1. `setSomething` 不可呼叫任何方法或屬性,也不可作為參數傳入函式或方法。 > 2. `setSomething` 不能被指派給型別為 T 的變數,其中 T 不為 `unknown` 或 `any`。 我們用實例來看看是什麼意思: ##### 1. 不可呼叫任何方法或屬性,也不可作為參數傳入函式或方法 當我們將一個被註記為 `unknown` 的 `safeParseJSON()`,指派給 `parseToSafeJSON`,這時 `parseToSafeJSON` 其實無法呼叫任何方法或屬性。 ```typescript= function safeParseJSON(jsonStr: string): unknown { return JSON.parse(jsonStr); } const randomJSONStr = `{ "msg": "Hello TS", "isGood": true }`; const parseToJSON = JSON.parse(randomJSONStr); parseToJSON.msg; // Success const parseToSafeJSON = safeParseJSON(randomJSONStr); parseToSafeJSON.msg; // Error: Object is of type 'unknown' ``` ##### 2. 不能被指派給型別為 T 的變數,其中 T 不為 `unknown` 或 `any` 還記得在[變數指派限制](https://hackmd.io/@elzuoc/Skd3Uree3#%E8%AE%8A%E6%95%B8%E6%8C%87%E6%B4%BE%E9%99%90%E5%88%B6)中,我們就有舉過 `unknown` 指派的例子嗎? 我們宣告一個型別為 `T` 的變數,這裡的 `T` ,可以是任何不為 `unknown` 或 `any` 的型別,我們就以 `number` 型別為例: ```typescript= let unknownType: unknown; let setNumber: number; setNumber = unknownType; // Error ``` 以結果來看,我們確實無法將 `unknownType` 指派給 `setNumber`。 -- #### `unknown` 型別的複合特性 任意型別與 `unknown` 型別進行 **`&`(交集)**,就會成為任意型別: ```typescript= type T1 = unknown & null; // null type T2 = unknown & undefined; // undefined type T3 = unknown & null & undefined; // never type T4 = unknown & string; // string type T5 = unknown & string[]; // string[] type T6 = unknown & unknown; // unknown type T7 = unknown & any; // any ``` 除了 `any` 外,任意型別與 `unknown` 型別進行 **`|`(聯集)**,就會成為 `unknown`: ```typescript= type T1 = unknown | null; // unknown type T2 = unknown | undefined; // unknown type T3 = unknown | null & undefined; // unknown type T4 = unknown | string; // unknown type T5 = unknown | string[]; // unknown type T6 = unknown | unknown; // unknown type T7 = unknown | any; // any ``` --- ### Reference - [讓TypeScript成為你全端開發的ACE!- Maxwell Huang](https://www.books.com.tw/products/0010859134) - [讓 TypeScript 成為你全端開發的 ACE! 系列 - 第 11 屆 iThome 鐵人賽](https://ithelp.ithome.com.tw/users/20120614/ironman/2685) - [TypeScript Official Site](https://www.typescriptlang.org/docs) --- > 系列:[`跑完 JS30 就接著認識 TypeScript 入門`](https://hackmd.io/@elzuoc?tags=%5B%22%E8%B7%91%E5%AE%8C+JS30+%E5%B0%B1%E6%8E%A5%E8%91%97%E8%AA%8D%E8%AD%98+TypeScript+%E5%85%A5%E9%96%80%22%5D) > 上一篇:[Day04:型別系統 - 明文型別 Literal Types 與 型別化名](https://hackmd.io/@elzuoc/rkcMxJJx3) > 下一篇:[Day06:型別系統 - 複合型別 Composite Types](https://hackmd.io/@elzuoc/rJRtRjUx3) ###### tags: [`跑完 JS30 就接著認識 TypeScript 入門`](https://hackmd.io/@elzuoc?tags=%5B%22%E8%B7%91%E5%AE%8C+JS30+%E5%B0%B1%E6%8E%A5%E8%91%97%E8%AA%8D%E8%AD%98+TypeScript+%E5%85%A5%E9%96%80%22%5D) ###### Created on ∥ March 21, 2023 ###### 文章若有任何錯誤,還請不吝給予留言指正,謝謝大家!