keyof 和 typeof 在 TypeScript 中有什么用?
简化版
keyof 获取类型的键名联合,typeof 在类型位置可以从变量值推导出类型。二者常配合配置对象使用,让值和类型保持同步。
详细版
keyof:
interface User {
id: number;
name: string;
}
type UserKey = keyof User; // 'id' | 'name'
类型位置的 typeof:
const config = {
theme: 'dark',
pageSize: 20,
};
type Config = typeof config;
type ConfigKey = keyof typeof config;
常用于限制参数只能是对象已有 key:
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
完整版教学
一、keyof 把对象类型的键变成联合类型
如果一个对象类型有 id 和 name,keyof 得到的就是 'id' | 'name'。这让 key 也能被类型系统约束。
function pickUser(key: keyof User) {}
传入不存在的 key 会被编译器拦住。
二、typeof 有两个世界
JavaScript 运行时的 typeof 用来判断值类型。TypeScript 类型位置的 typeof 用来获取变量的静态类型。
const user = { id: 1, name: 'Tom' };
type User = typeof user;
这里不会在运行时执行,它只是类型推导。
三、keyof typeof 的典型用法
const routes = {
home: '/',
about: '/about',
} as const;
type RouteName = keyof typeof routes;
这样新增或删除 routes 的 key,RouteName 自动变化,避免维护两份枚举。
四、泛型 get 函数体现类型关联
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
返回值类型会根据 key 精确变化。传 'name' 返回 string,传 'id' 返回 number。这是 TS 类型系统非常实用的能力。
五、面试追问与工程落地
面试官常会追问 keyof any 是什么。它等价于 string | number | symbol,因为 JS 对象属性 key 的类型最终就是这三类。数组的 key 还会包含数字索引和一些数组方法名,所以对数组使用 keyof 时要小心。
另一个追问是 as const 的作用。没有 as const 时,对象里的字符串值可能被拓宽成 string;加上后会保留字面量类型并把属性推成 readonly。这对从配置对象推导联合类型很关键。
工程里 keyof typeof 很适合替代手写枚举,比如路由名、权限码、主题 key、埋点事件名。值和类型来自同一份配置,减少“配置改了类型忘改”的问题。
六、把键空间、值空间和运行时枚举分开
keyof 的结果不总是字面量键列表。字符串索引签名的键结果通常包含 string | number,因为 JavaScript 会把数字属性访问转成字符串;数字索引签名则得到 number。另一方面,运行时 Object.keys 只返回自有可枚举字符串键,不会因为静态类型写得更窄就自动返回 (keyof T)[]。
type StringMap = { [key: string]: boolean }
type MapKey = keyof StringMap // string | number
const config = { dark: true, pageSize: 20 } as const
type Config = typeof config
type ConfigKey = keyof Config // 'dark' | 'pageSize'
type PageSize = Config['pageSize'] // 20
| 表达式 | 所处世界 | 示例结果 |
|---|---|---|
typeof value | JavaScript 运行时 | 'object'、'string' 等字符串 |
type X = typeof value | TypeScript 类型位置 | 值的静态类型 |
keyof X | TypeScript 类型位置 | 属性键联合 |
X[K] | TypeScript 类型位置 | 键 K 对应的值类型 |
上例配置有 2 个键,keyof typeof config 会自动产生 2 个成员;增加第 3 个配置项后,键联合也同步增加。as const 让 pageSize 保留字面量 20 且属性只读,而不是拓宽成一般的 number。
记忆顺序:
typeof从值走到类型,keyof从对象类型走到键联合,T[K]再从键走到对应值类型。
七、常见误区与追问
- 误区:keyof 永远只会得到字符串字面量。 数字键、Symbol 键和索引签名都会改变结果,字符串索引签名常得到
string | number。 - 误区:类型位置的 typeof 会生成运行时代码。 它只查询静态类型,编译后被擦除,与 JavaScript 的 typeof 表达式用途不同。
- 误区:Object.keys(obj) 的返回类型一定是 Array
。 对象运行时可能有类型声明外的额外键,因此标准签名通常返回string[]。 - 追问:keyof any 是什么? 它是
string | number | symbol,对应 JavaScript 可用的属性键类型。 - 追问:为什么 keyof 字符串索引类型包含 number?
obj[0]在 JavaScript 中会按字符串键访问,所以数字也可命中字符串索引签名。 - 追问:as const 对 keyof 本身有什么帮助? 键通常本就保留,但它能保住值的字面量和 readonly 信息,便于继续从
typeof obj[keyof typeof obj]推导值联合。 - 追问:怎样写类型安全的 get 函数? 用
<T, K extends keyof T>(obj: T, key: K): T[K]同时约束键并关联返回值。
八、加强记忆
keyof 是“从类型拿键”,类型位置的 typeof 是“从值拿类型”。组合起来就是“从配置对象自动生成键名类型”。
遇到配置推导题就沿“值 → typeof 类型 → keyof 键 → T[K] 值”这条链推演,避免混淆运行时 API。