僅知道鍵和值的類型,如何定義 TS 對(duì)象類型?
在學(xué)習(xí) TS 的過程中,你遇到過類似的錯(cuò)誤么?
let user = {}
user.id = "TS001" // 類型“{}”上不存在屬性“id”。
user.name = "阿寶哥" // 類型“{}”上不存在屬性“name”。
除了 any “大招” 之外,你還知道幾種解決方案?閱讀完本文,你將會(huì)找到一些答案。
這是小王他的月薪是包含了基本工資和月度獎(jiǎng)金,這是小郭他的月薪只包含合同工資。
const Wang = {
baseSalary: 10000, // 基本工資
monthlyBonus: 2000 // 月度獎(jiǎng)金
};
const Guo = {
contractSalary: 15000 // 合同工資
}
這里我們可以實(shí)現(xiàn)一個(gè) calculateSalary 函數(shù)來(lái)計(jì)算他們的薪資,計(jì)算邏輯實(shí)現(xiàn)起來(lái)很簡(jiǎn)單。但問題來(lái)了,在 TS 中如何定義該函數(shù)參數(shù)的類型呢?
function calculateSalary(salaryObject: ???) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
給你 3 秒鐘的時(shí)間思考一下。你想到答案了么?其中一種方案是使用索引簽名。當(dāng)我們僅知道對(duì)象鍵和值的類型時(shí),就可以使用索引簽名來(lái)定義該對(duì)象的類型。
這是索引簽名的語(yǔ)法:
{ [key: KeyType]: ValueType }
其中 Key 的類型,只能是 string,number,symbol 或模版字面量類型,而值的類型可以是任意類型。
interface Dictionary {
[key: boolean]: string;
}
其中模版字面量類型是 TypeScript 4.1 版本引入的新類型,結(jié)合索引簽名我們可以定義更強(qiáng)大的類型:
interface PropChangeHandler {
[key: `${string}Changed`]: () => void;
}
let handlers: PropChangeHandler = {
idChanged: () => {}, // Ok
nameChanged: () => {}, // Ok
ageChange: () => {} // Error
};
了解索引簽名的語(yǔ)法之后,我們就可以輕松地定義出 salaryObject 參數(shù)的類型:
function calculateSalary(salaryObject: { [key: string]: number }) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
有些時(shí)候,在定義對(duì)象類型時(shí),會(huì)含有一些已知和未知的鍵,這時(shí)我們可以結(jié)合索引簽名來(lái)定義該對(duì)象的類型:
interface Options {
[key: string]: string | number | boolean;
timeout: number; // 已知的鍵
}
const options: Options = {
timeout: 1000,
errorMessage: 'The request timed out!',
isSuccess: false
};
在使用索引簽名時(shí),你可能會(huì)遇到這些困惑:
interface NumbersNames {
[key: string]: string
}
const names: NumbersNames = {
'1': 'one',
'2': 'two',
'3': 'three'
};
const value1 = names['1'] // Ok
const value2 = names[1] // Ok
type N0 = keyof NumbersNames // string | number
- 為什么可以通過字符串 1 和數(shù)字 1 來(lái)訪問對(duì)應(yīng)的屬性值。
- 為什么 keyof NumbersNames 返回的 string 和 number 類型組成的聯(lián)合類型。
這是因?yàn)楫?dāng)用作屬性訪問器中的鍵時(shí),JavaScript 會(huì)隱式地將數(shù)字強(qiáng)制轉(zhuǎn)換為字符串,TypeScript 也會(huì)執(zhí)行這種轉(zhuǎn)換。
除了使用索引簽名之外,我們還可以使用 TS 內(nèi)置的工具類型 Record 類型來(lái)定義 calculateSalary 函數(shù)的參數(shù)類型:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
function calculateSalary(salaryObject: Record<string, number>) {
let total = 0;
for (const name in salaryObject) {
total += salaryObject[name];
}
return total;
}
那么索引簽名和 Record 工具類型有什么區(qū)別呢?在一些場(chǎng)合下,它們都能定義出期望的類型。
const user1: Record<string, string> = { name: "阿寶哥" }; // Ok
const user2: { [key: string]: string } = { name: "阿寶哥" }; // Ok
對(duì)于索引簽名來(lái)說(shuō),其鍵的類型,只能是 string,number,symbol 或模版字面量類型。而 Record 工具類型,鍵的類型可以是字面量類型或字面量類型組成的聯(lián)合類型:
type User1 = {
[key: "id"]: string; // Error
};
type User2 = {
[key: "id" | "name"]: string; // Error
};
type User3 = Record<"id", string>; // Ok
type User4 = Record<"id" | "name", string>; // Ok
const user: User4 = {
id: "TS001",
name: "阿寶哥",
};
看完以上內(nèi)容,你學(xué)會(huì)索引簽名和 Record 工具類型了么?