🔑 TypeScript 进阶:如何精准获取对象的所有 Key?
🤔 为什么这不仅仅是 Object.keys() 的问题?
在 JavaScript 中,Object.keys(obj) 返回一个字符串数组,这没问题。但在 TypeScript 中,如果你直接用它来访问对象属性,编译器会报错:
typescript
const user = { name: "Alice", age: 25 };
// ❌ 错误演示
const keys = Object.keys(user); // 类型是 string[]
keys.forEach((key) => {
console.log(user[key]);
// ^^^ Error: Element implicitly has an 'any' type because index expression is not of type 'number'.
// TS 不知道 key 是 "name" 还是 "age",它只认为 key 是任意字符串
});
我们需要的是:既拿到运行时的 Key 值,又保留编译时的类型信息。
📂 目录
- [🛠️ 运行时获取:
Object.keys的类型断言](#🛠️ 运行时获取:Object.keys 的类型断言) - [🧩 编译时提取:
keyof操作符](#🧩 编译时提取:keyof 操作符) - [🚀 高级玩法:泛型与工具类型结合](#🚀 高级玩法:泛型与工具类型结合)
- [⚠️ 常见陷阱:索引签名与可选属性](#⚠️ 常见陷阱:索引签名与可选属性)
- [💡 总结](#💡 总结)
1. 🛠️ 运行时获取:Object.keys 的类型断言
如果你需要在代码逻辑中遍历对象,必须解决 Object.keys 返回 string[] 的问题。
✅ 方法一:使用 as 断言(简单粗暴)
告诉 TS:"相信我,这些 key 一定是这个对象的 Key"。
typescript
const user = { name: "Alice", age: 25 };
// 将 string[] 断言为 (keyof typeof user)[]
const keys = Object.keys(user) as Array<keyof typeof user>;
keys.forEach((key) => {
console.log(key); // "name" | "age"
console.log(user[key]); // ✅ 类型安全!自动推断为 string | number
});
解析:
typeof user:获取变量user的类型{ name: string; age: number }。keyof typeof user:提取该类型的键联合类型"name" | "age"。Array<...>:将其包裹为数组类型。
✅ 方法二:封装通用辅助函数(推荐复用)
为了避免每次都要写 as,可以封装一个类型安全的 helper 函数。
typescript
function getObjectKeys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}
// 使用
const keys = getObjectKeys(user);
keys.forEach((key) => {
// key 的类型自动推断为 "name" | "age"
console.log(user[key]);
});
2. 🧩 编译时提取:keyof 操作符
有时候,我们不需要在运行时遍历,只需要在定义类型 时引用某个对象的 Key。这时 keyof 是神器。
✅ 场景 1:限制函数参数只能是对象的 Key
typescript
interface User {
id: number;
name: string;
email: string;
}
// 第二个参数必须是 User 的某个 key
function getUserProperty(user: User, key: keyof User) {
return user[key];
}
getUserProperty({ id: 1, name: "Bob", email: "bob@test.com" }, "name"); // ✅ OK
getUserProperty({ id: 1, name: "Bob", email: "bob@test.com" }, "phone"); // ❌ Error: Argument of type '"phone"' is not assignable to parameter of type '"id" | "name" | "email"'.
✅ 场景 2:基于现有类型生成新类型
typescript
type UserKeys = keyof User; // "id" | "name" | "email"
// 创建一个只包含部分属性的新类型
type PartialUser = Pick<User, "name" | "email">;
3. 🚀 高级玩法:泛型与工具类型结合
在编写通用库或复杂组件时,我们经常需要动态处理对象属性。
✅ 场景:实现一个通用的 omit 函数
移除对象中的指定字段,并保留剩余字段的类型。
typescript
function omit<T extends object, K extends keyof T>(
obj: T,
keys: K[],
): Omit<T, K> {
const result = { ...obj };
keys.forEach((key) => {
delete result[key];
});
return result as Omit<T, K>;
}
const user = { id: 1, name: "Alice", password: "123" };
const safeUser = omit(user, ["password"]);
// safeUser 的类型自动推断为: { id: number; name: string; }
// password 属性已从类型中移除,访问 safeUser.password 会报错 ✅
✅ 场景:映射类型(Mapped Types)
将所有属性变为可选,或修改属性类型。
typescript
// 将 User 的所有属性值变为 string
type StringifiedUser = {
[K in keyof User]: string;
};
// 结果等价于: { id: string; name: string; email: string; }
4. ⚠️ 常见陷阱:索引签名与可选属性
❌ 陷阱 1:索引签名 [key: string]: any
如果对象定义了索引签名,keyof 的结果会变成 string | number,而不是具体的 Key 列表。
typescript
interface Config {
[key: string]: any; // 索引签名
theme: string;
}
type ConfigKeys = keyof Config; // 结果是 string | number,而不是 "theme"
原因 :因为你可以用任意字符串访问
config["anything"],所以 TS 认为 Key 可以是任意字符串。
❌ 陷阱 2:可选属性 ?
keyof 依然会包含可选属性的 Key,但访问时需要注意 undefined。
typescript
interface User {
name: string;
age?: number;
}
type Keys = keyof User; // "name" | "age"
function getVal(u: User, k: Keys) {
const val = u[k];
// val 的类型是 string | number | undefined
// 因为 age 可能是 undefined
}
❌ 陷阱 3:Object.keys 不包含原型链上的属性
Object.keys() 只返回对象自身 的可枚举属性。如果属性在原型链上,或者被设置为 enumerable: false,它将不会被获取到。这在大多数业务场景下是符合预期的,但需知晓。
5. 💡 总结
| 需求场景 | 推荐方案 | 关键点 |
|---|---|---|
| 运行时遍历对象 | Object.keys(obj) as (keyof typeof obj)[] |
使用断言修复类型 |
| 封装通用工具 | function getKeys<T>(obj: T): (keyof T)[] |
利用泛型复用逻辑 |
| 限制参数范围 | keyof InterfaceName |
编译时类型约束 |
| 类型转换/映射 | [K in keyof T]: ... |
映射类型语法 |
🚀 博主寄语 :
TypeScript 的强大之处在于类型与值的同步 。
单纯使用
Object.keys只是 JS 的思维,而结合keyof和typeof才是 TS 的正道。记住口诀 :
运行遍历用 Keys,
断言 keyof 保类型。
编译约束 keyof 出,
泛型配合更无敌。
索引签名需谨慎,
具体键名才清晰。
希望这篇文档能帮你彻底搞懂 TS 中获取对象 Key 的技巧!如果有疑问,欢迎在评论区留言。👇
喜欢这篇文章吗?记得点赞、收藏、转发哦! ❤️