一句话
keyof T 取一个"类型"的所有属性名,得到一个字符串联合类型。
ini
type User = {
id: number;
name: string;
email: string;
};
type UserKeys = keyof User; // "id" | "name" | "email"
注意:keyof 操作的是"类型",不是变量 。你不能 keyof user(实例),只能 keyof User(类型),或者 keyof typeof user。
三个最高频的用法
① 泛型约束:安全地按属性名取值(最常用)
vbnet
function get<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const u: User = { id: 1, name: "张三", email: "a@b.com" };
const id = get(u, "id"); // 类型是 number ✅
const name = get(u, "name"); // 类型是 string ✅
get(u, "xxx"); // ❌ 报错:"xxx" 不在 keyof User 里
这就是 K extends keyof T + 索引访问 T[K] 的组合:
K extends keyof T→ 限制 key 只能是 T 的合法属性名;T[K]→ 自动推导出该属性的值类型 (传"id"得到number,传"name"得到string)。
这几乎是所有"类型安全对象操作"的模板 ------lodash.get、pick、omit 的 TS 实现都靠它。
② 映射类型:批量改造属性(Partial/Pick 的底层)
scala
// 手写一个 Partial:把所有属性变可选
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type PartialUser = MyPartial<User>;
// { id?: number; name?: string; email?: string }
// 手写一个 Pick:只挑几个属性
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
type IdAndName = MyPick<User, "id" | "name">;
// { id: number; name: string }
{ [K in keyof T]: ... } 就是"遍历 T 的每个键",这是 TS 工具类型(Partial/Required/Readonly/Pick/Omit/Record)的共同骨架。
③ keyof typeof:从"值"反推键
ini
const config = {
host: "localhost",
port: 8080,
};
type ConfigKey = keyof typeof config; // "host" | "port"
typeof config→ 把值变成类型{ host: string; port: number };keyof→ 再取键 →"host" | "port"。
这个组合在实际项目里很常见:定义了一个常量对象,想拿到它的键当联合类型用。
几个细节/坑
① 可选属性也会被算进 keyof
ini
type A = { id?: number };
type K = keyof A; // "id"(可选不影响取键)
② 索引签名会得到 string | number
ini
type Dict = { [key: string]: number };
type K = keyof Dict; // string | number
③ 数组/对象的 keyof 是"方法名"
lua
type K = keyof string[]; // number | "length" | "push" | "pop" | ...
④ keyof any = string | number | symbol
ini
type AnyKey = keyof any; // string | number | symbol
对照 Go
Go 没有 keyof 这种"编译期取结构体字段名"的能力。最接近的是运行时反射:
css
// Go:运行时拿字段名
t := reflect.TypeOf(User{})
field, _ := t.FieldByName("Name")
差别在于:
- Go 的 reflect 是运行时、有性能开销、靠字符串匹配;
- TS 的
keyof是编译期类型运算 ,零运行时成本,还能让编译器提前报错(get(u, "xxx")直接编译不过)。
TS 的 keyof 相当于"把 Go 反射的字段名检查,提前到编译期" 。
一句话总结
keyof T= T 的属性名联合;K extends keyof T+T[K]= 类型安全的"按键取值";[K in keyof T]= 遍历属性批量改造。 记住这三个组合,TS 工具类型你就能自己写了。