Try   HackMD

never:各種型別的 subtype。假設型別註記為 boolean ,還是有可能被型別推論為 never ,表示這個變數或函式有一些條件狀況可能未被處理。
any:通常只在導入 TS 初步除錯時使用,開發專案時 盡量別!使!用!
unknown:似 any ,但 unknown 型別的變數值,只能指派給 anyunknown 型別的變數。

Never

通常有兩種情況會使用此型別:

  1. 無法跳脫函式
  2. 必須中斷程式執行的狀況

沒辦法跳脫函式的狀況,最直觀就是函式內發生了無窮迴圈:

const loopForever = () => { while(true) { // 程式會卡在這無法跳脫 } }

不過通常很難第一時間就發現,程式因某個條件而導致的無窮迴圈。看到這裡,有些人可能會以為是 TS 檢測時進行 型別推論 才會需要 never 型別來警告。

其實不然,再看一個會造成 never 型別的範例:

function checkData(x: string | number): boolean { if (typeof x === 'string') { return true; } else if (typeof x === 'number') { return false; } }

在嚴格模式下,會造成某些條件(else)沒有返回值,這種函式就會被推論為 boolean 下的 never 型別。

提醒never 是各種型別的 subtype。

如果傳值符合條件,回傳了 trueboolean ),就與型別推論結果(never)產生衝突,導致 TS 報錯,因此,比較常見的作法,我們會在最後 return 一個 never 型別的東西。

那麼,我們試著在函式最後,再加上一段語法,針對不符合任何條件的結果,拋出一個錯誤訊息。

事實上這個動作其實很常見,尤其是在 try...catch 的語法中。因此,我們通常將它寫成 function

function throwError(msg: string): never { throw new Error(msg); }

回憶一下 <Day02:型別系統 - 原始型別 Primitive Types> 我們有說明過的 void

你會發現這裡並不是使用 void 作為型別註記,因為 throw new Error 還是算一個回傳值。

所以如果我們這麼做:

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 型別類似,兩者相同之處在於,變數可以接收任何型別的值:

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();
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 型別變數值,指派給任何型別的變數:

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 型別變數值,指派給任何型別的變數:

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

setSomething = unknownType;

這時,

  1. setSomething 不可呼叫任何方法或屬性,也不可作為參數傳入函式或方法。
  2. setSomething 不能被指派給型別為 T 的變數,其中 T 不為 unknownany

我們用實例來看看是什麼意思:

1. 不可呼叫任何方法或屬性,也不可作為參數傳入函式或方法

當我們將一個被註記為 unknownsafeParseJSON(),指派給 parseToSafeJSON,這時 parseToSafeJSON 其實無法呼叫任何方法或屬性。

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 不為 unknownany

還記得在變數指派限制中,我們就有舉過 unknown 指派的例子嗎?

我們宣告一個型別為 T 的變數,這裡的 T ,可以是任何不為 unknownany 的型別,我們就以 number 型別為例:

let unknownType: unknown; let setNumber: number; setNumber = unknownType; // Error

以結果來看,我們確實無法將 unknownType 指派給 setNumber

unknown 型別的複合特性

任意型別與 unknown 型別進行 &(交集),就會成為任意型別:

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

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


系列:跑完 JS30 就接著認識 TypeScript 入門
上一篇:Day04:型別系統 - 明文型別 Literal Types 與 型別化名
下一篇:Day06:型別系統 - 複合型別 Composite Types

tags: 跑完 JS30 就接著認識 TypeScript 入門
Created on ∥ March 21, 2023
文章若有任何錯誤,還請不吝給予留言指正,謝謝大家!