keyof 和 typeof 操作符
概述
keyof 和 typeof 是 TypeScript 中两个重要的类型操作符,它们分别用于获取对象类型的键和从值推导类型。keyof 操作符可以获取对象类型的所有键组成的联合类型,而 typeof 操作符可以从值中提取类型信息。这两个操作符经常结合使用,是构建类型安全代码和类型工具的基础。
keyof 操作符
基本概念
keyof 操作符用于获取对象类型的所有键(属性名)组成的联合类型。它返回一个字符串字面量类型的联合类型,包含对象类型的所有键名。
基本语法
typescript
// 基本语法:keyof T
// T 是对象类型
// 返回类型:T 的所有键组成的联合类型
interface User {
name: string;
age: number;
email: string;
}
// 获取 User 类型的所有键
type UserKeys = keyof User;
// 类型:'name' | 'age' | 'email'基本用法示例
typescript
interface Config {
host: string;
port: number;
ssl: boolean;
database: {
name: string;
pool: number;
};
}
// 获取所有键
type ConfigKeys = keyof Config;
// 类型:'host' | 'port' | 'ssl' | 'database'
// 使用 keyof 创建类型安全的访问函数
function getConfigValue<T extends keyof Config>(key: T): Config[T] {
// 实现省略
return {} as Config[T];
}
// 使用示例
const host = getConfigValue('host'); // 类型:string
const port = getConfigValue('port'); // 类型:number
const ssl = getConfigValue('ssl'); // 类型:boolean
// ❌ 类型错误:'invalid' 不是有效的键
// const invalid = getConfigValue('invalid');keyof 与索引访问类型结合
keyof 经常与索引访问类型结合使用,创建灵活的类型工具:
typescript
interface User {
name: string;
age: number;
email: string;
}
// 获取所有属性值的类型
type UserValues = User[keyof User];
// 类型:string | number
// 创建类型安全的属性访问函数
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// 使用示例
const user: User = {
name: 'John',
age: 30,
email: 'john@example.com'
};
const name = getProperty(user, 'name'); // 类型:string
const age = getProperty(user, 'age'); // 类型:number
// ❌ 类型错误:'invalid' 不是有效的键
// const invalid = getProperty(user, 'invalid');keyof 与映射类型结合
keyof 是映射类型的基础,用于遍历对象类型的所有键:
typescript
interface User {
name: string;
age: number;
email: string;
}
// 将所有属性变为可选
type PartialUser = {
[K in keyof User]?: User[K];
};
// 类型:{ name?: string; age?: number; email?: string }
// 将所有属性变为只读
type ReadonlyUser = {
readonly [K in keyof User]: User[K];
};
// 类型:{ readonly name: string; readonly age: number; readonly email: string }
// 提取特定键的类型
type PickUser = {
[K in 'name' | 'age']: User[K];
};
// 类型:{ name: string; age: number }keyof 与类
keyof 也可以用于类类型,获取类的公共属性名:
typescript
class User {
name: string;
age: number;
private email: string; // 私有属性不会被 keyof 获取
constructor(name: string, age: number, email: string) {
this.name = name;
this.age = age;
this.email = email;
}
getName(): string {
return this.name;
}
}
// 获取类的公共属性名
type UserKeys = keyof User;
// 类型:'name' | 'age' | 'getName'
// 注意:私有属性和受保护属性不会被包含typeof 操作符
基本概念
typeof 操作符用于从值中提取类型信息。在 TypeScript 中,typeof 有两种用法:
- 类型查询:
typeof value- 获取值的类型 - 类型守卫:
typeof value === 'string'- 运行时类型检查(在类型守卫中使用)
这里我们主要讨论类型查询的用法。
基本语法
typescript
// 基本语法:typeof value
// value 是一个值(变量、常量、函数等)
// 返回类型:value 的类型
const name = "TypeScript";
type NameType = typeof name;
// 类型:string
const age = 30;
type AgeType = typeof age;
// 类型:30(数字字面量类型)基本用法示例
typescript
// 从变量获取类型
const user = {
name: 'John',
age: 30,
email: 'john@example.com'
};
type UserType = typeof user;
// 类型:{ name: string; age: number; email: string }
// 从函数获取类型
function greet(name: string): string {
return `Hello, ${name}!`;
}
type GreetFunction = typeof greet;
// 类型:(name: string) => string
// 从数组获取类型
const numbers = [1, 2, 3, 4, 5];
type NumbersType = typeof numbers;
// 类型:number[]
// 从对象字面量获取类型
const config = {
host: 'localhost',
port: 3000,
ssl: true
} as const; // 使用 as const 获取字面量类型
type ConfigType = typeof config;
// 类型:{ readonly host: 'localhost'; readonly port: 3000; readonly ssl: true }typeof 与 as const 结合
使用 as const 可以将值转换为只读的字面量类型:
typescript
// 不使用 as const
const colors = ['red', 'green', 'blue'];
type ColorsType = typeof colors;
// 类型:string[]
// 使用 as const
const colorsConst = ['red', 'green', 'blue'] as const;
type ColorsConstType = typeof colorsConst;
// 类型:readonly ['red', 'green', 'blue']
// 获取数组元素的类型
type Color = typeof colorsConst[number];
// 类型:'red' | 'green' | 'blue'typeof 与函数
从函数中提取类型信息:
typescript
// 函数定义
function createUser(name: string, age: number) {
return {
name,
age,
id: Math.random().toString()
};
}
// 获取函数类型
type CreateUserFunction = typeof createUser;
// 类型:(name: string, age: number) => { name: string; age: number; id: string }
// 获取函数返回类型
type User = ReturnType<typeof createUser>;
// 类型:{ name: string; age: number; id: string }
// 获取函数参数类型
type CreateUserParams = Parameters<typeof createUser>;
// 类型:[name: string, age: number]typeof 与枚举
从枚举中提取类型信息:
typescript
enum Status {
Pending = 'pending',
Active = 'active',
Inactive = 'inactive'
}
// 获取枚举类型
type StatusType = typeof Status;
// 类型:typeof Status(枚举对象类型)
// 获取枚举值的类型
type StatusValue = Status;
// 类型:Status(枚举值类型:Status.Pending | Status.Active | Status.Inactive)
// 获取枚举键的类型
type StatusKeys = keyof typeof Status;
// 类型:'Pending' | 'Active' | 'Inactive'keyof 和 typeof 结合使用
keyof 和 typeof 经常结合使用,从值中提取键的类型:
基本结合使用
typescript
// 定义常量对象
const userRoles = {
admin: 'admin',
user: 'user',
guest: 'guest'
} as const;
// 获取对象类型
type UserRolesType = typeof userRoles;
// 类型:{ readonly admin: 'admin'; readonly user: 'user'; readonly guest: 'guest' }
// 获取键的类型
type UserRoleKeys = keyof typeof userRoles;
// 类型:'admin' | 'user' | 'guest'
// 获取值的类型
type UserRoleValues = typeof userRoles[keyof typeof userRoles];
// 类型:'admin' | 'user' | 'guest'实际应用场景
场景 1:类型安全的配置对象
typescript
// 定义配置对象
const appConfig = {
api: {
baseUrl: 'https://api.example.com',
timeout: 5000
},
features: {
auth: true,
logging: false
}
} as const;
// 从配置对象提取类型
type AppConfig = typeof appConfig;
type ApiConfig = typeof appConfig.api;
type FeaturesConfig = typeof appConfig.features;
// 获取配置键的类型
type ConfigKeys = keyof typeof appConfig;
// 类型:'api' | 'features'
type ApiKeys = keyof typeof appConfig.api;
// 类型:'baseUrl' | 'timeout'
// 创建类型安全的配置访问函数
function getConfig<T extends keyof typeof appConfig>(
section: T
): typeof appConfig[T] {
return appConfig[section];
}
// 使用示例
const api = getConfig('api');
// 类型:{ readonly baseUrl: 'https://api.example.com'; readonly timeout: 5000 }
const features = getConfig('features');
// 类型:{ readonly auth: true; readonly logging: false }场景 2:类型安全的路由定义
typescript
// 定义路由配置
const routes = {
home: '/',
about: '/about',
contact: '/contact',
blog: '/blog'
} as const;
// 获取路由键的类型
type RouteKey = keyof typeof routes;
// 类型:'home' | 'about' | 'contact' | 'blog'
// 获取路由值的类型
type RoutePath = typeof routes[RouteKey];
// 类型:'/' | '/about' | '/contact' | '/blog'
// 创建类型安全的路由导航函数
function navigate(route: RouteKey): void {
const path = routes[route];
// 导航逻辑
console.log(`Navigating to ${path}`);
}
// 使用示例
navigate('home'); // ✅ 正确
navigate('about'); // ✅ 正确
// navigate('invalid'); // ❌ 类型错误场景 3:类型安全的事件系统
typescript
// 定义事件处理器映射
const eventHandlers = {
click: (event: MouseEvent) => {
console.log('Click event', event);
},
keydown: (event: KeyboardEvent) => {
console.log('Keydown event', event);
},
resize: (event: UIEvent) => {
console.log('Resize event', event);
}
} as const;
// 获取事件名称的类型
type EventName = keyof typeof eventHandlers;
// 类型:'click' | 'keydown' | 'resize'
// 获取事件处理器的类型
type ClickHandler = typeof eventHandlers['click'];
// 类型:(event: MouseEvent) => void
// 创建类型安全的事件注册函数
function on<T extends EventName>(
event: T,
handler: typeof eventHandlers[T]
): void {
// 注册事件处理器
}
// 使用示例
on('click', (event) => {
// event 类型:MouseEvent
console.log(event.clientX, event.clientY);
});
on('keydown', (event) => {
// event 类型:KeyboardEvent
console.log(event.key, event.code);
});场景 4:类型安全的 API 客户端
typescript
// 定义 API 端点配置
const apiEndpoints = {
users: {
list: '/api/users',
get: (id: number) => `/api/users/${id}`,
create: '/api/users',
update: (id: number) => `/api/users/${id}`,
delete: (id: number) => `/api/users/${id}`
},
posts: {
list: '/api/posts',
get: (id: number) => `/api/posts/${id}`,
create: '/api/posts'
}
} as const;
// 获取资源名称的类型
type ResourceName = keyof typeof apiEndpoints;
// 类型:'users' | 'posts'
// 获取特定资源的操作类型
type UserOperations = keyof typeof apiEndpoints.users;
// 类型:'list' | 'get' | 'create' | 'update' | 'delete'
// 创建类型安全的 API 客户端
type ApiClient = {
[R in ResourceName]: {
[O in keyof typeof apiEndpoints[R]]: typeof apiEndpoints[R][O]
}
};
// 使用示例
const client: ApiClient = apiEndpoints;
const usersList = client.users.list; // 类型:'/api/users'
const getUser = client.users.get; // 类型:(id: number) => string使用示例
示例 1:类型安全的表单验证
typescript
// 定义表单字段配置
const formFields = {
username: {
required: true,
minLength: 3,
maxLength: 20
},
email: {
required: true,
pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
age: {
required: false,
min: 18,
max: 100
}
} as const;
// 获取字段名称的类型
type FieldName = keyof typeof formFields;
// 类型:'username' | 'email' | 'age'
// 创建类型安全的验证函数
function validateField<T extends FieldName>(
field: T,
value: string | number
): boolean {
const config = formFields[field];
if (config.required && (value === '' || value === null || value === undefined)) {
return false;
}
if ('minLength' in config && typeof value === 'string') {
return value.length >= config.minLength;
}
if ('min' in config && typeof value === 'number') {
return value >= config.min;
}
return true;
}
// 使用示例
validateField('username', 'john'); // ✅ 正确
validateField('email', 'test@example.com'); // ✅ 正确
validateField('age', 25); // ✅ 正确
// validateField('invalid', 'value'); // ❌ 类型错误示例 2:类型安全的主题系统
typescript
// 定义主题配置
const themes = {
light: {
background: '#ffffff',
text: '#000000',
primary: '#007bff'
},
dark: {
background: '#1a1a1a',
text: '#ffffff',
primary: '#0d6efd'
}
} as const;
// 获取主题名称的类型
type ThemeName = keyof typeof themes;
// 类型:'light' | 'dark'
// 获取主题配置的类型
type ThemeConfig = typeof themes[ThemeName];
// 类型:{ readonly background: '#ffffff'; readonly text: '#000000'; readonly primary: '#007bff' }
// | { readonly background: '#1a1a1a'; readonly text: '#ffffff'; readonly primary: '#0d6efd' }
// 获取主题属性的类型
type ThemeKey = keyof typeof themes.light;
// 类型:'background' | 'text' | 'primary'
// 创建类型安全的主题访问函数
function getThemeValue<T extends ThemeName, K extends ThemeKey>(
theme: T,
key: K
): typeof themes[T][K] {
return themes[theme][key];
}
// 使用示例
const lightBg = getThemeValue('light', 'background');
// 类型:'#ffffff'
const darkText = getThemeValue('dark', 'text');
// 类型:'#ffffff'示例 3:类型安全的国际化系统
typescript
// 定义翻译对象
const translations = {
en: {
greeting: 'Hello',
farewell: 'Goodbye',
welcome: 'Welcome'
},
zh: {
greeting: '你好',
farewell: '再见',
welcome: '欢迎'
},
ja: {
greeting: 'こんにちは',
farewell: 'さようなら',
welcome: 'いらっしゃいませ'
}
} as const;
// 获取语言代码的类型
type Language = keyof typeof translations;
// 类型:'en' | 'zh' | 'ja'
// 获取翻译键的类型
type TranslationKey = keyof typeof translations.en;
// 类型:'greeting' | 'farewell' | 'welcome'
// 创建类型安全的翻译函数
function translate<T extends Language, K extends TranslationKey>(
lang: T,
key: K
): typeof translations[T][K] {
return translations[lang][key];
}
// 使用示例
const greetingEn = translate('en', 'greeting');
// 类型:'Hello'
const greetingZh = translate('zh', 'greeting');
// 类型:'你好'
const greetingJa = translate('ja', 'greeting');
// 类型:'こんにちは'类型检查示例
常见错误
typescript
interface User {
name: string;
age: number;
}
// ❌ 错误:在非对象类型上使用 keyof
// type StringKeys = keyof string; // 类型错误
// ❌ 错误:使用不存在的键
// type InvalidKey = keyof User & 'invalid'; // 类型错误
// ❌ 错误:typeof 不能用于类型
// type Wrong = typeof User; // User 是类型,不是值
// ❌ 错误:在类型上使用 typeof(应该用于值)
// interface Config { ... }
// type Wrong = typeof Config; // Config 是类型,不是值正确写法
typescript
interface User {
name: string;
age: number;
}
// ✅ 正确:在对象类型上使用 keyof
type UserKeys = keyof User;
// 类型:'name' | 'age'
// ✅ 正确:从值中提取类型
const user = {
name: 'John',
age: 30
};
type UserType = typeof user;
// 类型:{ name: string; age: number }
// ✅ 正确:结合使用 keyof 和 typeof
type UserTypeKeys = keyof typeof user;
// 类型:'name' | 'age'
// ✅ 正确:从常量对象提取类型
const config = {
host: 'localhost',
port: 3000
} as const;
type ConfigType = typeof config;
// 类型:{ readonly host: 'localhost'; readonly port: 3000 }
type ConfigKeys = keyof typeof config;
// 类型:'host' | 'port'注意事项
提示
keyof操作符只能用于对象类型,不能用于原始类型(string、number 等)typeof操作符用于值,不能用于类型- 使用
as const可以将值转换为只读的字面量类型,这对于类型提取非常有用 keyof只获取公共属性,私有属性和受保护属性不会被包含keyof和typeof结合使用可以创建强大的类型安全工具typeof在类型位置使用时是类型查询,在值位置使用时是 JavaScript 的 typeof 操作符
注意
keyof操作符返回的是字符串字面量类型的联合类型typeof操作符在类型位置和值位置的行为不同,需要注意区分- 使用
as const时,对象会变成只读的,所有属性都会变成字面量类型 keyof对于联合类型的行为可能不符合预期,需要特别注意- 复杂的
keyof typeof组合可能会影响类型检查性能
重要
keyof和typeof是 TypeScript 类型系统的核心操作符- 理解这两个操作符对于掌握高级类型和类型级编程至关重要
keyof和typeof经常结合使用,是构建类型工具的基础- 这两个操作符不会影响运行时的性能,因为类型信息在编译时会被擦除
信息
keyof 操作符的优势:
- 类型安全:在编译时确保键访问的正确性
- 代码复用:避免硬编码字符串字面量
- 自文档化:类型定义本身就是文档
- 重构友好:当对象类型改变时,相关类型会自动更新
typeof 操作符的优势:
- 类型推导:从值中自动提取类型,减少重复定义
- 类型同步:值的类型改变时,提取的类型自动更新
- 字面量类型:结合
as const可以获取精确的字面量类型 - 代码简化:避免手动定义重复的类型
keyof 和 typeof 的应用场景:
- 类型安全的配置对象访问
- 类型安全的路由系统
- 类型安全的事件系统
- 类型安全的 API 客户端
- 类型安全的表单验证
- 类型安全的主题系统
- 类型安全的国际化系统
- 工具类型的实现
相关链接
- 索引访问类型 - 了解如何使用索引访问类型与 keyof 结合
- 映射类型 - 学习映射类型与 keyof 的结合使用
- 条件类型 - 了解条件类型与 keyof 的结合
- 泛型约束 - 学习如何在泛型约束中使用 keyof
- 对象类型 - 了解对象类型的基础概念
- 字面量类型 - 了解字面量类型与 typeof 的关系
- TypeScript 官方文档 - keyof 操作符
- TypeScript 官方文档 - typeof 操作符