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