iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 16
3
自我挑戰組

Typescript 初心者手札系列 第 16

【Day 16】TypeScript 資料型別 - 特殊型別(下)- Any & Unknown

  • 分享至 

  • xImage
  •  

閱讀今天的文章前,先回顧一下昨天的學習,回答看看:

  • never 和 void 的差異為何?
  • never 是所有型別的子型別,這會讓 never 擁有什麼特性?

如果有點不清楚答案的話,可以看看 Day15 的文章喔!

Any 任意型別

在 TS 中,any 型別佔有特殊的地位,只要遇到 any 型別,TS 就會跳過檢查系統,不會進行型別檢查。因此,any 可以兼容所有的型別(包括 any 自己),所有的型別像是基礎型別、物件、陣列、函式等都可以被賦值給 any ,any 也能賦值給其他任何型別。

let value: any;

//可以接收任何型別賦值
value = true;             // 布林型別,OK
value = 18;               // 數字型別,OK
value = "Hello World";    // 字串型別,OK
value = [];               // 陣列型別,OK
value = {};               // 基礎物件型別,OK
value = null;             // null型別,OK
value = undefined;        // undefined型別,OK
value = new TypeError();  // Error物件,OK
value = Symbol("type");   // Symbol型別,OK

// 也可以賦值給任何型別
let value1: unknown = value;   // unknown型別,OK
let value2: any = value;       // any型別,OK
let value3: boolean = value;   // 布林型別,Error
let value4: number = value;    // 數字型別,Error
let value5: string = value;    // 字串型別,Error
let value6: object = value;    // 物件型別,Error
let value7: any[] = value;     // 空陣列,Error
let value8: void = value;      // 空值,Error 

由於 TS 對於 any 型別不會做任何檢查,因此,任何資料型別的屬性或方法都會通過。

let value: any;

value.length;  // 求字串長度,OK
value.trim();  // 去除前後空格,OK
value();       // 執行函式,OK
new value();   // 創建物件,OK
value[0];      // 取陣列元素,OK

這樣似乎太沒有節操了(誤),這樣很容易寫出編譯通過但實際運行時有問題的程式碼。基本上,在 TS 中 any 能少用就少用,如果真的遇到,也應盡量主動做型別註記。

Any 型別推論

倘若創建一個變數 foo (變數可以重新賦值),且不註記型別:

let foo 

把滑鼠滑到 foo 上方,會發現變數 foo 預設推論就是 any 型別 let foo: any。在之前的文章中,也有討論到幾次 any 型別推論可能出現的情境像是:

  1. 一般宣告下的函式,其參數的型別隱性推論結果是 any (Day 08)
  2. 沒有註記的空陣列,其型別推論為 any[] (Day 10)

倘若開發者希望完全禁止出現預設推論型別為 any 型別的狀況,可以在 tsconfig.json 中設定 noImplicitAny 為 true。

Unknown 未知型別

TS3.0 版本新增了 unknown 型別,可以看成是 any 型別的安全版本。什麼意思呢?

unknown 和 any 一樣可以接受任何型別賦值

let value: unknown;

value = true;             // 布林型別,OK
value = 18;               // 數字型別,OK
value = "Hello World";    // 字串型別,OK
value = [];               // 陣列型別,OK
value = {};               // 基礎物件型別,OK
value = null;             // null型別,OK
value = undefined;        // undefined型別,OK
value = new TypeError();  // Error物件,OK
value = Symbol("type");   // Symbol型別,OK

但是若賦值 unknown 型別給其他型別又會發生什麼事呢?

let value: unknown;

let value1: unknown = value;   // unknown型別,OK
let value2: any = value;       // any型別,OK
let value3: boolean = value;   // 布林型別,Error
let value4: number = value;    // 數字型別,Error
let value5: string = value;    // 字串型別,Error
let value6: object = value;    // 物件型別,Error
let value7: any[] = value;     // 空陣列,Error
let value8: void = value;      // 空值,Error

答案是除了 unknown 和 any 之外,其他型別都會報錯。若是直接推論,結果亦是合理的,因為只有能夠容納任何型別的 any 和自己能接受自己,unknown 代表對於值一無所知,因此無法賦值給明確的型別。

如果我們操作 unknown 型別的方法或屬性又會如何呢?

let value: unknown;

value.length;   // Error
value.trim();   // Error
value();        // Error
new value();    // Error
value[0];       // Error

當變數 value 註記為 unknown 型別時,所有的屬性或方法都會報錯,由此可知,從 any 型別換成 unknown 型別,最大的差異在於預設情況下允許操作屬性或方法變成禁止操作屬性或方法。這也是 Unknown 型別會出現的原因。TS 創建了比 any 型別更安全的 unknown 型別,避免 any 型別較難控制的行為,將 unknown 型別調整為無法任意操作其屬性或方法。那要怎麼做才能操作 unknown 型別的屬性或方法呢?

答案就是昨天提到過的型別檢測(type guard),unknown 型別要進行限縮才能使用屬性或方法。

例如:有一變數 isUnknown 為 unknown 型別,倘若直接賦值給數字型別的變數 value ,編譯器會報錯。除非限縮型別後,將 isUnknown 進行型別檢測,判斷若為數字,才可進行賦值

let isUnknown: unknown;

//Error: Type 'unknown' is not assignable to type 'number'
let value: number = isUnknown;

//限縮型別後,PASS!
if(typeof isUnknown === 'number'){
    value = isUnknown
}

而除了型別檢測之外,還有另外一種將型別限縮的方式,那就是型別斷言,舉例來說:

const value: unknown = "Hello World";
const someString: string = value as string;
const otherString = someString.toUpperCase();  // "HELLO WORLD"

要注意的是:TS 不會執行任何特殊檢查來確保型別斷言是有效的。如果斷言錯誤的型別,雖然編譯器不會報錯,但執行時仍然會有錯誤

來個重點整理吧:any v.s. unknown

unknown 和 any 相同的地方在於兩者都可以接受任意型別,但 any 可以賦值給任何型別,unknown 只能賦值給any 和自己。另外,unknown 型別必須執行型別斷言或型別檢查(type guard)才能操作其屬性或方法且通過編譯,而 any 則可以任意操作其屬性或方法。

Unknown 的複合型別

含有 Unknown 的聯合型別

倘若在聯合型別中含有 unknown 型別,會發生什麼事呢?

type UnionType1 = unknown | null;       // unknown
type UnionType2 = unknown | undefined;  // unknown
type UnionType3 = unknown | string;     // unknown
type UnionType4 = unknown | number[];   // unknown
type UnionType5 = unknown | any;        // any

答案是 unknown 覆蓋了幾乎所有型別(any 除外)。正如上面所提,所有型別都可以賦值給 unknown,換句話說,unknown 型別的變數可以接收任何型別的值,因此,unknown | string 就可以簡化成 unknown。

含有 Unknown 的交集型別

在交集型別中,每個型別都會吸收 unknown,也就是說任何型別和 unknown 產生交集,結果不會有任何改變

type IntersectionType1 = unknown & null;       // null
type IntersectionType2 = unknown & undefined;  // undefined
type IntersectionType3 = unknown & string;     // string
type IntersectionType4 = unknown & number[];   // number[]
type IntersectionType5 = unknown & any;        // any

IntersectionType3:unknown & string 為例,代表其值為 unknown 型別也是字串型別。但由於每種型別都可賦值給 unknown,所以 unknown 的交集型別就是字串。

小結

今天介紹了 TS 的 any 和 unknown 型別以及其特性,明天將會來探討 TS 的通用型別(Generic Types),那我們明天見囉!

參考資料:
官方網站- Basic types
The unknown Type in TypeScript


上一篇
【Day 15】TypeScript 資料型別 - 特殊型別(上)- Never
下一篇
【Day 17】TypeScript 資料型別 - 通用型別(Generic Types)
系列文
Typescript 初心者手札30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
harry xie
iT邦研究生 1 級 ‧ 2022-06-13 09:31:29

使用 any 賦值的那段程式碼後面是不是有寫錯?後面的型別不會出現 error

// 也可以賦值給任何型別
let value1: unknown = value;   // unknown型別,OK
let value2: any = value;       // any型別,OK
let value3: boolean = value;   // 布林型別,Error
let value4: number = value;    // 數字型別,Error
let value5: string = value;    // 字串型別,Error
let value6: object = value;    // 物件型別,Error
let value7: any[] = value;     // 空陣列,Error
let value8: void = value;      // 空值,Error 

我要留言

立即登入留言