unknown 和 any 經常放在一起比較,它們十分相似,但相較於 any, unknown 更為安全,因為它不像 any 一樣任何型別都可以接受,甚至操作不存在的屬性。那究竟在什麼情況下可以使用 unknown 呢?它可以接受什麼型別呢?
本篇將來介紹 unknown
和其使用時機,並會跟 any 一起做對照,在此也把之前介紹 any 的文章放在下方
🔗 文章傳送門:Day15 - any 任意型別
unknown 在 TypeScript 3.0 版本時被引入,被作為 any 更安全的替代
,所謂的更安全是指被定義為 unknown 的變數、參數,在進行任何近一步的操作前都會經過型別檢查機制,相反地 any 則不需要
這張圖說明了 TypeScript 中不同型別之間的可指派性(assignability)
,請先專注看 unknown 和 any
每列(row)表示來源型別,每行(column)表示目標型別。表格的每一格表示是否可以將「來源型別」指派派給「目標型別」
藍色勾勾(✓):來源型別可以被指派給目標型別
綠色勾勾(✓):tsconfig.json 中的 strictNullChecks 關閉時才能被指派
紅色叉叉(✗):來源型別不能指派給目標型別
總結上圖,我們可以得知:
若看不懂沒有關係,透過範例會較好理解
當不確定資料的型別時
當你從外部來源(如 API 呼叫、第三方函式庫等)接收資料時,型別可能是不確定的。這時使用 unknown 可以安全地接收任何型別的資料
作為 any 的替代
unknown 是相較於 any 更安全的替代品。any 雖然很靈活,但它會繞過 TypeScript 的型別系統,可能導致相關的錯誤產生
透過以下幾個範例,來了解 unknown 的特性
unknown 可以接受任何型別的值(any 也是)
let a: any;
let u: unknown;
// ✅ 以下都是 Pass
a = 10;
a = 'Hello';
a = true;
a = { key: 'value' };
u = 10;
u = 'Hello';
u = true;
u = { key: 'value' };
unknown 不能直接對值進行操作
function f1(v1: any) {
v1.b(); // ✅ Pass
}
function f2(v2: unknown) {
v2.b(); // ❌ 'a' is of type 'unknown'.
v2.foo.bar; // 同上
v2.trim(); // 同上
v2(); // 同上
new v2(); // 同上
v2[1]; // 同上
}
unknown 能指派給 any 或它自己,但不能直接指派給其他型別,除非進行型別斷言或型別守衛
let value1: unknown;
let value2: any = value1; // ✅ Pass,unknown 可以指派給 any
let value3: unknown = value1; // ✅ Pass,unknown 可以指派給 unknown
let str: string;
str = value1; // ❌ unknown 不能直接指派給字串,Type 'unknown' is not assignable to type 'string'.
let num: number = value1; // ❌ unknown 不能直接指派給數字,Type 'unknown' is not assignable to type 'number'.
let booleanVal: boolean = value1; // ❌ ...
let objectVal: object = value1; // ❌ ...
let arr: any[] = value1; // ❌ ...
let f1: Function = value1; // ❌ ...
// 需要進行型別斷言或型別守衛才能賦值給其他型別
if (typeof value1 === 'string') {
str = value1; // ✅ Pass,已經檢查過是字串
}
在對 unknown 變數進行任何操作前,需要先透過型別守衛 Type Guards 進行 Narrowing,如使用 typeof、instanceof 、Type Predicate (型別謂詞)等,確定具體型別是什麼,後續的操作才有意義
let value: unknown;
if (typeof value === 'string') {
console.log(value.toUpperCase()); // ✅ Pass
}
class Person {
constructor(public name: string) {
// ... 略 ...
};
sayHello() {
return `Hello ${this.name}`;
};
}
class Animal {
constructor(public species: string) {
// ... 略 ...
};
makeSound() {
return `${this.species} makes a sound`;
};
}
function greet(value: unknown) {
if (value instanceof Person) { // ✅ Pass
console.log(value.sayHello());
} else if (value instanceof Animal) { // ✅ Pass
console.log(value.makeSound());
} else {
console.log('Unknown type');
}
}
const john = new Person('John');
const dog = new Animal('Dog');
greet(john); // Hello John
greet(dog); // Dog makes a sound
greet(123); // Unknown type
Type Predicate (型別謂詞)
是屬於自定義型別守衛 (User-Defined Type Guards)的一種parameterName
is Type
】組成,parameterName
是函式中的參數名稱,Type
就是你定義的型別下方範例中的 animal is Dog
就是 Type Predicate
type Animals = {
type: string;
};
type Dog = Animals & {
bark: () => void;
};
function isDog(animal: unknown): animal is Dog {
return (animal as Animals).type === 'dog';
}
console.log(isDog({ type: 'dog' })); // true
console.log(isDog({ type: 'human' })); // false
const unknownVal: unknown = 1;
const numVal: number = unknownVal as number;
console.log(numVal + 1); // ✅ Pass, 2
console.log(unknownVal + 1) // ❌ 'value1' is of type 'unknown'.
unknown | any |
---|---|
有型別檢查 | 跳過型別檢查 |
可以接受任何型別 | 可以接受任何型別的值 |
只能被指派給 unknown 或 any,或需要通過型別斷言後指派給其他型別 | 可以指派給任何型別 |
在未進行型別守衛或型別斷言前,不能對變數進行任何操作 | 可以對變數進行任意操作,沒有限制 |
💡 若版本允許,使用 unknown 會比 any 更為安全唷
每天的內容有推到 github 上喔