
本教程探讨如何在 typescript 中定义一个对象类型,使其键值受限于预定义集合,同时允许这些键是可选的,而非强制。通过利用 typescript 的映射修饰符 `?`,我们能够灵活地构建嵌套对象类型,避免因缺少部分键而导致的类型错误,从而提升类型定义的灵活性和实用性。
在 TypeScript 中定义复杂的数据结构时,我们经常需要创建一种对象类型,其键名必须来自某个预定义的集合,但同时又希望这些键是可选的,即不必全部存在。这在构建配置对象、API 响应或灵活的数据字典时尤为常见。本文将深入探讨如何使用 TypeScript 的映射类型(Mapped Types)及其修饰符来优雅地解决这一问题。
假设我们有两组常量,分别定义了两种类型的标识符:
export const ABC = {
A: 'A',
B: 'B',
C: 'C',
} as const;
export const DEF = {
D: 'D',
E: 'E',
F: 'F',
} as const;
// 提取这些常量的值作为联合类型
export type AbcTypes = (typeof ABC)[keyof typeof ABC]; // 'A' | 'B' | 'C'
export type DefTypes = (typeof DEF)[keyof typeof DEF]; // 'D' | 'E' | 'F'现在,我们的目标是构建一个嵌套对象 MyNewDictionary,其结构如下:
最初的尝试可能会是这样:
type MyNewDictionary = {
[pKey in AbcTypes]: {
[eKey in DefTypes]: {
onClick: () => void;
onCancel: () => void;
}
}
};然而,当我们尝试创建一个 MyNewDictionary 类型的实例,但只填充部分键时,TypeScript 会立即报错:
const dictionary: MyNewDictionary = {
[ABC.A]: {
[DEF.D]: {
onClick: () => null,
onCancel: () => null,
}
}
};
// 错误信息示例:
// Type '{ D: { onClick: () => null; onCancel: () => null; }; }' is missing the following properties from type '{ D: { onClick: () => void; onCancel: () => void; }; E: { onClick: () => void; onCancel: () => void; }; F: { onClick: () => void; onCancel: () => void; }; }': 'E', 'F'
// 并且外层也可能报错,因为缺少 'B', 'C'这个错误表明,当前 MyNewDictionary 的定义强制要求所有 AbcTypes 和 DefTypes 中的键都必须存在。使用 Partial<T> 作用于整个 MyNewDictionary 类型可能无法完全解决嵌套层级的可选性问题,因为它只会使顶层属性可选,而嵌套的映射类型内部仍然是强制的。
TypeScript 提供了强大的映射修饰符来改变映射类型中属性的特性。其中,? 修饰符用于将属性标记为可选(Optional)。通过在映射类型中直接应用 ?,我们可以精确控制哪些属性是可选的。
将 ? 修饰符应用于上述 MyNewDictionary 的定义,可以使其内部的键变为可选:
type MyNewDictionaryCorrected = {
[pKey in AbcTypes]?: { // 使第一层键可选
[eKey in DefTypes]?: { // 使第二层键可选
onClick: () => void;
onCancel: () => void;
}
}
};在这个修正后的类型定义中:
现在,使用 MyNewDictionaryCorrected 类型,我们可以创建只包含部分键的对象,而不会遇到类型错误:
export const ABC = {
A: 'A',
B: 'B',
C: 'C',
} as const;
export const DEF = {
D: 'D',
E: 'E',
F: 'F',
} as const;
export type AbcTypes = (typeof ABC)[keyof typeof ABC];
export type DefTypes = (typeof DEF)[keyof typeof DEF];
type MyNewDictionaryCorrected = {
[pKey in AbcTypes]?: {
[eKey in DefTypes]?: {
onClick: () => void;
onCancel: () => void;
}
}
};
// 示例 1:只包含一个顶层键和一个内层键
const dictionary1: MyNewDictionaryCorrected = {
[ABC.A]: {
[DEF.D]: {
onClick: () => console.log('A.D onClick'),
onCancel: () => console.log('A.D onCancel'),
}
}
};
console.log(dictionary1); // 类型检查通过
// 示例 2:包含多个顶层键,但每个顶层键下只包含部分内层键
const dictionary2: MyNewDictionaryCorrected = {
[ABC.A]: {
[DEF.D]: { onClick: () => {}, onCancel: () => {} },
[DEF.F]: { onClick: () => {}, onCancel: () => {} },
},
[ABC.C]: {
[DEF.E]: { onClick: () => {}, onCancel: () => {} },
}
};
console.log(dictionary2); // 类型检查通过
// 示例 3:尝试使用非定义的键,会报错
// const invalidDictionary: MyNewDictionaryCorrected = {
// 'X': { // 错误:类型 '"X"' 不可分配给类型 'AbcTypes'
// [DEF.D]: { onClick: () => {}, onCancel: () => {} }
// }
// };通过这个例子,我们可以看到 MyNewDictionaryCorrected 成功地实现了我们的需求:键名受限于 AbcTypes 和 DefTypes,但它们都是可选的。
掌握映射类型及其修饰符是 TypeScript 高级用法中的重要一环,它能帮助我们构建出更健壮、更灵活且易于维护的代码。
以上就是TypeScript 教程:在映射类型中实现可选且受限的对象键的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号