Skip to content

keyof 和 typeof 操作符

概述

keyoftypeof 是 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 有两种用法:

  1. 类型查询typeof value - 获取值的类型
  2. 类型守卫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 结合使用

keyoftypeof 经常结合使用,从值中提取键的类型:

基本结合使用

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 只获取公共属性,私有属性和受保护属性不会被包含
  • keyoftypeof 结合使用可以创建强大的类型安全工具
  • typeof 在类型位置使用时是类型查询,在值位置使用时是 JavaScript 的 typeof 操作符

注意

  • keyof 操作符返回的是字符串字面量类型的联合类型
  • typeof 操作符在类型位置和值位置的行为不同,需要注意区分
  • 使用 as const 时,对象会变成只读的,所有属性都会变成字面量类型
  • keyof 对于联合类型的行为可能不符合预期,需要特别注意
  • 复杂的 keyof typeof 组合可能会影响类型检查性能

重要

  • keyoftypeof 是 TypeScript 类型系统的核心操作符
  • 理解这两个操作符对于掌握高级类型和类型级编程至关重要
  • keyoftypeof 经常结合使用,是构建类型工具的基础
  • 这两个操作符不会影响运行时的性能,因为类型信息在编译时会被擦除

信息

keyof 操作符的优势

  • 类型安全:在编译时确保键访问的正确性
  • 代码复用:避免硬编码字符串字面量
  • 自文档化:类型定义本身就是文档
  • 重构友好:当对象类型改变时,相关类型会自动更新

typeof 操作符的优势

  • 类型推导:从值中自动提取类型,减少重复定义
  • 类型同步:值的类型改变时,提取的类型自动更新
  • 字面量类型:结合 as const 可以获取精确的字面量类型
  • 代码简化:避免手动定义重复的类型

keyof 和 typeof 的应用场景

  • 类型安全的配置对象访问
  • 类型安全的路由系统
  • 类型安全的事件系统
  • 类型安全的 API 客户端
  • 类型安全的表单验证
  • 类型安全的主题系统
  • 类型安全的国际化系统
  • 工具类型的实现

相关链接

基于 VitePress 构建