繼昨天的泛型函式,今天要來介紹「泛型約束」
keyof
關鍵字我們知道泛型中的 T
,代表一個型別參數(Type Parameter
),它可代進任意輸入的型別
我們可以使用 extends
關鍵字來定義「泛型約束」。告訴 TypeScript ,泛型的「型別參數」必須符合特定的型別結構
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length);
return arg;
}
loggingIdentity(3); // ❌ Error
loggingIdentity({ length: 10, value: 3 }); // ✅ Pass
loggingIdentity
函式中的泛型約束 <Type extends Lengthwise>
約束了任何傳入的 arg
都必須有一個 length
屬性
loggingIdentity(3)
因為不符合參數必須有 length 這個屬性的規定,所以報錯,錯誤訊息如下Argument of type 'number' is not assignable to parameter of type 'Lengthwise'
附上圖解
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = {
a: 1,
b: 2,
c: 3,
d: 4
};
getProperty(x, "a"); // ✅ Pass
getProperty(x, "m"); // ❌ Error
這個函式中有兩個型別參數,分別是 Type
Key
Type
可以接受任何類型的值Key
受到 Type 的鍵( = 物件的屬性名)的約束。這也代表 Key 必須是 Type 中存在的鍵,也就是 a, b, c, d,這個約束防止函式被傳入任何不存在的鍵
這也是為何 getProperty(x, 'm')
會報錯,因為 x
裡沒有 m
這個屬性
keyof
是 TypeScript 的操作符,用來取得「物件型別」的所有「鍵」的聯合(union)
這是 TypeScript 中處理物件屬性時很好用的操作符,確保在編譯時,對物件屬性的引用就已是安全的
interface Person {
name: string;
age: number;
}
type PersonKeys = keyof Person; // 'name' | 'age'
滑鼠移到上面這段會出現下方的推斷
宣告一個變數指向 PersonKeys
,值只能是 name 或 age
雖然陣列和元組等也屬於「物件型別」的一種,但在 keyof
的情境中,「物件型別」
通常是指有具名屬性的物件結構,常見於介面(Interface)
、類別(Class)
和型別別名(Type Alias)
但這不代表 TypeScript 禁止 keyof 用在陣列和元組等其他物件型別,只是這樣做沒什麼意義(等等看下面範例就會知道了😆),TypeScript 對它們的處理方式與一般物件型別略有不同
let numbers: number[] = [1, 2, 3];
type ArrayKeys = keyof typeof numbers; // type ArrayKeys = number | "length" | "push" | "pop" | ...
當你在陣列中使用 keyof 時,推斷出的是陣列操作方法名稱的 union
let tuple: [string, number] = ["hello", 42];
type TupleKeys = keyof typeof tuple; // type TupleKeys = "0" | "1" | "length" | "push" | "pop" | ...
元組的 keyof 會回傳 index 和陣列操作方法的名稱
這樣做其實沒什麼意義,對吧
只是單純抱有實驗精神把實驗結果記錄一下😂
泛型介面(Generic Interface)的定義方式類似於泛型函式。你可以指定一個或多個型別參數,這些參數可以用於介面中的屬性、方法
interface Container<T> {
value: T;
add(value: T): void;
}
泛型類別(Generic Classes)會在 Class Name 後會加上型別參數,表示該 Class 是泛用的
class Box<T> {
private contents: T;
constructor(value: T) {
this.contents = value;
}
get(): T {
return this.contents;
}
set(value: T): void {
this.contents = value;
}
}
let stringBox = new Box<string>("hello"); // hello
let numberBox = new Box<number>(123); // 123
開發一個函式 filterItems
,該函式接受一個陣列物件格式,並回傳所有具有特定屬性的物件。使用「泛型約束」來確保每個物件都有包含該屬性
interface WithID {
id: string;
}
// 👇 調整為泛型函式,並使用 WithID 做為泛型約束的條件
function filterItems() {
return items.filter(item => item.id.startsWith("#"));
}
// ✅ Pass
const filteredItems = filterItems([{ id: '#a' }, { id: '123' }]);
// ❌ Error 因為陣列中的物件沒有 id 屬性
// const errorFilteredItems = filterItems([{ name: 1 }, { name: 2 }]);
每天的內容有推到 github 上喔