當我們使用 TypeScript 時,我們想利用它提供的類型系統限制代碼的方方面面,對象的鍵值,也不例外。
譬如我們有個對象存儲每個年級的人名,類型大概長這樣:
type Students = Record<string, string[]>;
理所當然地,數據就是長這樣:
const students: Students = {
Freshman: ["David", "John"],
sophomore: [],
Junior: ["Lily"],
Senior: ["Tom"],
};
限制對象鍵名為枚舉
上面數據類型的問題是,年級是有限的幾種可值取,而該對象上可任意添加屬性,這樣顯得數據不夠純粹。
所以我們新增枚舉,列出可取的值:
export enum Grade {
Freshman,
sophomore,
Junior,
Senior,
}
現在,把對象的鍵名限制為上面枚舉就行了。
- type Students = Record<string, string[]>;
+ type Students = Record<Grade, string[]>;
這樣我們的數據可寫成這樣:
const students: Students = {
[Grade.Freshman]: ["David", "John"],
[Grade.sophomore]: [],
[Grade.Junior]: ["Lily"],
[Grade.Senior]: ["Tom"],
// ❌ Object literal may only specify known properties, and 'blah' does not exist in type 'Students'.ts(2322)
blah: ["some one"],
};
這樣,限制住了對象身上鍵名的范圍,可以看到如果添加一個枚舉之外的鍵會報錯。
更加語義化的枚舉值
但上面的做法還是有不妥之處,因為枚舉值默認是從 0 開始的數字,這樣,作為鍵值就不夠語義了,這點從訪問對象的屬性時體現了出來:
修正我們的枚舉,用更加語義的文本作為其值:
export enum Grade {
Freshman = "Freshman",
sophomore = "sophomore",
Junior = "Junior",
Senior = "Senior",
}
此時再使用該枚舉時,得到的就不是無意義的數字了。
如果你願意,枚舉值也可以是中文,
export enum Grade {
Freshman = "大一萌新",
sophomore = "大二學弟",
Junior = "大三學妹",
Senior = "大四老司機",
}
使用時也是沒任何問題的:
鍵值可選
上面的類型定義還有個問題,即,它要求使用時對象包含枚舉中所有值,比如 sophomore
這個年級中並沒有人,可以不寫,但會報錯。
// ❌ Property 'sophomore' is missing in type '{ Freshman: string[]; Junior: string[]; Senior: string[]; }' but required in type 'Students'.ts(2741)
const students: Students = {
[Grade.Freshman]: ["David", "John"],
// [Grade.sophomore]: [],
[Grade.Junior]: ["Lily"],
[Grade.Senior]: ["Tom"],
};
所以,優化類型為可選:
type Students = Partial<Record<Grade, string[]>>;
限制對象的鍵名為數組中的值
假若可選的值不是通過枚舉定義,而是來自一個數組,
const grades = ["Freshman", "sophomore", "Junior", "Senior"];
這意味着我們需要提取數組中的值形成一個聯合類型。
首先利用const assertions 把數組轉元組(Tuple)類型,
const grades = <const>["Freshman", "sophomore", "Junior", "Senior"];
再利用 typeof
和 Lookup Types 得到最終的聯合類型:
// 實際為 type Keys = "Freshman" | "sophomore" | "Junior" | "Senior"
type Keys = typeof grades[number];
最后數據類型和數據可寫成:
type Students = Partial<Record<Keys, string[]>>;
const students: Students = {
Freshman: ["David", "John"],
Junior: ["Lily"],
Senior: ["Tom"],
};
須知這種形式下,對象的 key 與原數組中元素其實沒有語法層面的關聯,即,編輯器的「跳轉定義」是不可用的。
盡量還是保持代碼之間的關聯才能體現出 TypeScript 的作用,所以像這種只有類型約束而無法建立關聯的操作是不建議的。
相關資源
The text was updated successfully, but these errors were encountered: