在 TypeScript 开发中,keyof
和 typeof
是两个极具威力的类型操作符,它们能将运行时数据与类型系统无缝衔接,极大提升代码的灵活性和类型安全性。本文将从原理、用法到实战场景,全面解析这两个操作符的核心价值。
typeof
在 TypeScript 中有两种用途:
typeof 123
返回 "number"
)。 // 1. 从对象推导类型
const user = {
id: 1,
name: "Alice",
age: 25
};
// 使用 typeof 提取 user 的类型
type User = typeof user;
// 等价于:
// type User = {
// id: number;
// name: string;
// age: number;
// }
// 2. 从函数推导类型
function greet(name: string): string {
return `Hello, ${name}!`;
}
type GreetFunction = typeof greet;
// 等价于:
// type GreetFunction = (name: string) => string;
interface
结合:基于已有对象生成类型。 interface UserInterface {
id: number;
name: string;
}
// 将对象转为符合接口的类型
const apiResponse = {
id: 2,
name: "Bob"
};
type ApiResponseType = typeof apiResponse; // 自动推导
const userFromApi: UserInterface = apiResponse; // 类型兼容
keyof
操作符获取一个类型的所有键名,生成一个联合类型(字符串字面量类型)。
interface User {
id: number;
name: string;
age: number;
}
// 获取 User 的所有键
type UserKeys = keyof User;
// 等价于:
// type UserKeys = "id" | "name" | "age";
// 1. 泛型约束示例
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { id: 1, name: "Alice" };
const name = getProperty(user, "name"); // 类型为 string
// const invalid = getProperty(user, "address"); // 编译错误:键不存在
// 2. 动态属性访问
type UserProperty = User["name"]; // 等价于 string
type UserProperties = User[keyof User]; // 等价于 number | string
问题:动态访问对象属性时,如何避免运行时错误?
解决方案:
const config = {
theme: "dark",
fontSize: 14,
language: "en"
};
// 1. 提取 config 的类型
type Config = typeof config;
// 2. 获取所有合法键名
type ConfigKeys = keyof Config; // "theme" | "fontSize" | "language"
// 3. 安全访问函数
function getConfigValue<K extends keyof Config>(key: K): Config[K] {
return config[key];
}
const theme = getConfigValue("theme"); // 类型为 "dark"
// const invalid = getConfigValue("color"); // 编译错误
问题:如何基于现有类型生成新类型?
解决方案:
// 1. 定义原始类型
interface User {
id: number;
name: string;
age: number;
}
// 2. 使用 keyof 和 typeof 生成只读版本
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// 3. 生成可选版本
type PartialUser = {
[K in keyof User]?: User[K];
};
// 4. 生成 Pick 类型(提取部分属性)
type UserBasicInfo = Pick<User, "id" | "name">;
// 等价于:
// type UserBasicInfo = {
// id: number;
// name: string;
// }
Record
结合问题:如何定义键为特定类型、值为另一类型的对象?
解决方案:
// 1. 定义键名类型
type Status = "idle" | "loading" | "success" | "error";
// 2. 定义值类型
type StatusMessage = {
code: number;
text: string;
};
// 3. 使用 Record 和 keyof 生成映射
const statusMessages: Record<Status, StatusMessage> = {
idle: { code: 200, text: "Ready" },
loading: { code: 202, text: "Processing..." },
success: { code: 200, text: "Operation succeeded" },
error: { code: 500, text: "Internal server error" }
};
// 4. 安全访问
function getStatusMessage(status: Status): StatusMessage {
return statusMessages[status]; // 类型安全
}
避免过度使用 typeof
interface
或 type
。typeof
适合从已有值推导类型(如 API 响应、配置对象)。keyof
的限制
number
、string
)。keyof (A | B)
)。与 Partial
、Required
等工具类型结合
type OptionalUser = Partial<Pick<User, "name" | "age">> & Required<Pick<User, "id">>;
// 等价于:
// type OptionalUser = {
// id: number;
// name?: string;
// age?: number;
// }
通过本文的学习,我们掌握了 TypeScript 中 keyof
和 typeof
的核心用法:
typeof
:将运行时值转换为类型,实现类型复用。keyof
:从类型中提取键名,确保属性访问的安全性。这两个操作符是 TypeScript 类型体操的基石,能显著提升代码的类型安全性和开发效率。希望你在实际项目中灵活运用,写出更健壮的代码!