TypeScript精准获取对象的所有Key的方法
作者:yqcoder
为什么这不仅仅是Object.keys()的问题?
在 JavaScript 中,Object.keys(obj) 返回一个字符串数组,这没问题。但在 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 值,又保留编译时的类型信息。
1. 运行时获取:Object.keys的类型断言
如果你需要在代码逻辑中遍历对象,必须解决 Object.keys 返回 string[] 的问题。
方法一:使用as断言(简单粗暴)
告诉 TS:“相信我,这些 key 一定是这个对象的 Key”。
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 函数。
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
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:基于现有类型生成新类型
type UserKeys = keyof User; // "id" | "name" | "email" // 创建一个只包含部分属性的新类型 type PartialUser = Pick<User, "name" | "email">;
3. 高级玩法:泛型与工具类型结合
在编写通用库或复杂组件时,我们经常需要动态处理对象属性。
场景:实现一个通用的omit函数
移除对象中的指定字段,并保留剩余字段的类型。
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)
将所有属性变为可选,或修改属性类型。
// 将 User 的所有属性值变为 string
type StringifiedUser = {
[K in keyof User]: string;
};
// 结果等价于: { id: string; name: string; email: string; }
4. 常见陷阱:索引签名与可选属性
陷阱 1:索引签名[key: string]: any
如果对象定义了索引签名,keyof 的结果会变成 string | number,而不是具体的 Key 列表。
interface Config {
[key: string]: any; // 索引签名
theme: string;
}
type ConfigKeys = keyof Config; // 结果是 string | number,而不是 "theme"
原因:因为你可以用任意字符串访问 config["anything"],所以 TS 认为 Key 可以是任意字符串。
陷阱 2:可选属性?
keyof 依然会包含可选属性的 Key,但访问时需要注意 undefined。
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 的技巧!
以上就是TypeScript精准获取对象的所有Key的方法的详细内容,更多关于TypeScript获取对象所有Key的资料请关注脚本之家其它相关文章!
