---
# System prepended metadata

title: 跑完 JS30 就接著認識 TypeScript 入門｜Day05：型別系統 - Never、Any、Unknown
tags: [跑完 JS30 就接著認識 TypeScript 入門]

---

---
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
###### 文章若有任何錯誤，還請不吝給予留言指正，謝謝大家！

