閱讀今天的文章前,先回顧一下昨天的學習,回答看看:
- never 和 void 的差異為何?
- never 是所有型別的子型別,這會讓 never 擁有什麼特性?
如果有點不清楚答案的話,可以看看 Day15 的文章喔!
在 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 能少用就少用,如果真的遇到,也應盡量主動做型別註記。
倘若創建一個變數 foo (變數可以重新賦值),且不註記型別:
let foo
把滑鼠滑到 foo 上方,會發現變數 foo 預設推論就是 any 型別 let foo: any
。在之前的文章中,也有討論到幾次 any 型別推論可能出現的情境像是:
倘若開發者希望完全禁止出現預設推論型別為 any 型別的狀況,可以在 tsconfig.json 中設定 noImplicitAny 為 true。
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 型別,會發生什麼事呢?
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 產生交集,結果不會有任何改變
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
使用 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