typescript:第十四章_类型操作符

第十四章:类型操作符

TypeScript 提供了丰富的类型操作符,使我们能够灵活地查询、转换和操作类型。本章将详细介绍 keyof、typeof、in、is 等查询操作符,以及条件类型、映射类型等高级类型操作技术。掌握这些操作符,将使你能够编写出更加灵活和强大的类型定义。

keyof 操作符用于获取类型的所有键组成的联合类型。

interface Person {
  name: string;
  age: number;
  email: string;
}
 
type PersonKeys = keyof Person;
// 等价于: "name" | "age" | "email"
 
// 使用场景:类型安全的属性访问
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
 
const person: Person = {
  name: "Alice",
  age: 25,
  email: "alice@example.com"
};
 
const name = getProperty(person, "name");    // string 类型
const age = getProperty(person, "age");      // number 类型
// const invalid = getProperty(person, "gender");  // Error: "gender" 不是 Person 的键
// 字符串索引签名
interface StringDictionary {
  [key: string]: string;
}
 
type StringKeys = keyof StringDictionary;  // string | number
 
// 数字索引签名
interface NumberDictionary {
  [key: number]: string;
}
 
type NumberKeys = keyof NumberDictionary;  // number
 
// 结合使用
interface MixedDictionary {
  [key: string]: string | number;
  length: number;  // 必须兼容索引签名
}
 
type MixedKeys = keyof MixedDictionary;  // string | number | "length"
// 实现类型安全的对象选择
function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>;
  keys.forEach(key => {
    result[key] = obj[key];
  });
  return result;
}
 
interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}
 
const user: User = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  password: "secret",
  createdAt: new Date()
};
 
// 只选择安全显示的字段
const publicUser = pick(user, ["id", "name", "email"]);
// 类型: { id: number; name: string; email: string }

在类型上下文中,typeof 用于获取变量或表达式的类型。

// 获取对象类型
const person = {
  name: "Alice",
  age: 25,
  hobbies: ["reading", "coding"]
};
 
type PersonType = typeof person;
// {
//   name: string;
//   age: number;
//   hobbies: string[];
// }
 
// 获取函数类型
function greet(name: string, greeting?: string): string {
  return `${greeting ?? "Hello"}, ${name}!`;
}
 
type GreetFunction = typeof greet;
// (name: string, greeting?: string | undefined) => string
 
// 获取类实例类型
class User {
  constructor(public name: string, public age: number) {}
}
 
type UserInstance = typeof User.prototype;  // User
type UserConstructor = typeof User;  // new (name: string, age: number) => User
// 使用 const 断言获得更精确的类型
const config = {
  api: {
    endpoint: "/api/v1",
    timeout: 5000
  },
  features: {
    darkMode: true,
    notifications: false
  }
} as const;
 
type ConfigType = typeof config;
// {
//   readonly api: {
//     readonly endpoint: "/api/v1";
//     readonly timeout: 5000;
//   };
//   readonly features: {
//     readonly darkMode: true;
//     readonly notifications: false;
//   };
// }
 
// 提取嵌套类型
type ApiConfig = typeof config.api;
// { readonly endpoint: "/api/v1"; readonly timeout: 5000; }
// 运行时配置对象
const API_ENDPOINTS = {
  users: "/api/users",
  posts: "/api/posts",
  comments: "/api/comments"
} as const;
 
// 从运行时值创建类型
type EndpointPaths = typeof API_ENDPOINTS[keyof typeof API_ENDPOINTS];
// "/api/users" | "/api/posts" | "/api/comments"
 
// 类型安全的 API 客户端
async function fetchApi<T>(
  path: EndpointPaths,
  options?: RequestInit
): Promise<T> {
  const response = await fetch(path, options);
  return response.json();
}
 
// 使用
interface User {
  id: number;
  name: string;
}
 
const users = await fetchApi<User[]>(API_ENDPOINTS.users);

in 操作符用于遍历联合类型的每个成员,常用于创建映射类型。

// 基础映射类型 - 将所有属性变为只读
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
 
interface User {
  name: string;
  age: number;
}
 
type ReadonlyUser = Readonly<User>;
// { readonly name: string; readonly age: number; }
 
// 将所有属性变为可选
type Partial<T> = {
  [P in keyof T]?: T[P];
};
 
// 将所有属性变为必需
type Required<T> = {
  [P in keyof T]-?: T[P];  // -? 移除可选修饰符
};
 
// 只读数组类型
type ReadonlyArray<T> = {
  readonly [P in keyof T[]]: T[P];
};
// 使用 as 重映射键名
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
 
interface Person {
  name: string;
  age: number;
}
 
type PersonGetters = Getters<Person>;
// {
//   getName: () => string;
//   getAge: () => number;
// }
 
// 过滤特定类型的属性
type StringKeys<T> = {
  [K in keyof T as T[K] extends string ? K : never]: T[K];
};
 
interface Mixed {
  name: string;
  age: number;
  email: string;
  active: boolean;
}
 
type OnlyStrings = StringKeys<Mixed>;
// { name: string; email: string; }
// 根据属性创建对应的事件处理器类型
type EventHandlers<T> = {
  [K in keyof T as `on${Capitalize<string & K>}Change`]?: (
    newValue: T[K],
    oldValue: T[K]
  ) => void;
};
 
interface Settings {
  theme: "light" | "dark";
  fontSize: number;
  notifications: boolean;
}
 
type SettingsEventHandlers = EventHandlers<Settings>;
// {
//   onThemeChange?: (newValue: "light" | "dark", oldValue: "light" | "dark") => void;
//   onFontSizeChange?: (newValue: number, oldValue: number) => void;
//   onNotificationsChange?: (newValue: boolean, oldValue: boolean) => void;
// }
 
// 实现带有事件处理器的类
class ObservableSettings implements Settings {
  theme: "light" | "dark" = "light";
  fontSize: number = 14;
  notifications: boolean = true;
 
  private handlers: EventHandlers<Settings> = {};
 
  on<K extends keyof EventHandlers<Settings>>(
    event: K,
    handler: EventHandlers<Settings>[K]
  ): void {
    this.handlers[event] = handler;
  }
 
  set<K extends keyof Settings>(key: K, value: Settings[K]): void {
    const oldValue = this[key];
    this[key] = value;
 
    const handlerName = `on${key.charAt(0).toUpperCase() + key.slice(1)}Change` as const;
    const handler = this.handlers[handlerName];
    if (handler) {
      handler(value, oldValue);
    }
  }
}

is 关键字用于创建返回 boolean 的自定义类型保护函数。

// 基本类型保护
function isString(value: unknown): value is string {
  return typeof value === "string";
}
 
function isNumber(value: unknown): value is number {
  return typeof value === "number";
}
 
function processValue(value: unknown) {
  if (isString(value)) {
    // value 被缩小为 string 类型
    console.log(value.toUpperCase());
  } else if (isNumber(value)) {
    // value 被缩小为 number 类型
    console.log(value.toFixed(2));
  }
}
// 接口定义
interface Cat {
  type: "cat";
  name: string;
  meow: () => void;
}
 
interface Dog {
  type: "dog";
  name: string;
  bark: () => void;
}
 
interface Bird {
  type: "bird";
  name: string;
  chirp: () => void;
}
 
type Animal = Cat | Dog | Bird;
 
// 类型保护函数
function isCat(animal: Animal): animal is Cat {
  return animal.type === "cat";
}
 
function isDog(animal: Animal): animal is Dog {
  return animal.type === "dog";
}
 
function isBird(animal: Animal): animal is Bird {
  return animal.type === "bird";
}
 
// 使用类型保护
function makeSound(animal: Animal): void {
  if (isCat(animal)) {
    animal.meow();
  } else if (isDog(animal)) {
    animal.bark();
  } else if (isBird(animal)) {
    animal.chirp();
  }
}
// 检查对象是否有某个属性
function hasProperty<T extends string>(
  obj: unknown,
  prop: T
): obj is { [K in T]: unknown } {
  return typeof obj === "object" && obj !== null && prop in obj;
}
 
// 检查是否为 Promise
function isPromise<T>(value: unknown): value is Promise<T> {
  return value instanceof Promise || (
    typeof value === "object" &&
    value !== null &&
    "then" in value &&
    typeof (value as Promise<T>).then === "function"
  );
}
 
// 使用
async function resolveValue<T>(value: T | Promise<T>): Promise<T> {
  if (isPromise<T>(value)) {
    return value;
  }
  return value;
}

条件类型根据类型关系选择两种类型之一,语法类似三元运算符。

// 基本语法: T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
 
type T1 = IsString<string>;  // true
type T2 = IsString<number>;  // false
type T3 = IsString<"hello">; // true (字面量类型是 string 的子类型)
 
// 检查是否继承自某个类型
class Animal {
  name!: string;
}
 
class Dog extends Animal {
  bark() {}
}
 
class Car {
  brand!: string;
}
 
type IsAnimal<T> = T extends Animal ? true : false;
 
type A = IsAnimal<Dog>;  // true
type B = IsAnimal<Car>;  // false

当条件类型作用于泛型参数时,会自动分布到联合类型的每个成员。

// 分布式条件类型
type ToArray<T> = T extends any ? T[] : never;
 
// 对联合类型进行分布
type StringOrNumberArray = ToArray<string | number>;
// string[] | number[] (不是 (string | number)[])
 
// 防止分布式行为 - 使用元组包裹
type NonDistributed<T> = [T] extends [any] ? T[] : never;
type A = NonDistributed<string | number>;  // (string | number)[]
 
// 实用示例:过滤联合类型
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
 
type T = Exclude<"a" | "b" | "c", "a">;  // "b" | "c"
type U = Extract<"a" | "b" | "c", "a" | "d">;  // "a"

infer 用于在条件类型中推断类型变量。

// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
 
function getUser() {
  return { id: 1, name: "Alice" };
}
 
type User = ReturnType<typeof getUser>;  // { id: number; name: string; }
 
// 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
 
function updateUser(id: number, data: { name: string }) {}
 
type UpdateUserParams = Parameters<typeof updateUser>;
// [number, { name: string }]
 
// 提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : T;
 
type A = ElementType<string[]>;   // string
type B = ElementType<number[][]>; // number[]
 
// 提取 Promise 返回值
type Awaited<T> = T extends Promise<infer R> ? R : T;
 
type P = Awaited<Promise<string>>;  // string
type N = Awaited<number>;           // number
// 只读类型
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
 
// 可选类型
type Partial<T> = {
  [P in keyof T]?: T[P];
};
 
// 必需类型(移除可选)
type Required<T> = {
  [P in keyof T]-?: T[P];
};
 
// 可空类型
type Nullable<T> = {
  [P in keyof T]: T[P] | null;
};
 
// 代理包装
type Proxy<T> = {
  [P in keyof T]: { get(): T[P]; set(value: T[P]): void };
};
 
interface Person {
  name: string;
  age: number;
}
 
type ProxiedPerson = Proxy<Person>;
// {
//   name: { get(): string; set(value: string): void; };
//   age: { get(): number; set(value: number): void; };
// }
// 深度只读
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object 
    ? DeepReadonly<T[P]> 
    : T[P];
};
 
// 深度可选
type DeepPartial<T> = {
  [P in keyof T]?: T[P] extends object 
    ? DeepPartial<T[P]> 
    : T[P];
};
 
// 深度必需
type DeepRequired<T> = {
  [P in keyof T]-?: T[P] extends object 
    ? DeepRequired<T[P]> 
    : T[P];
};
 
interface Nested {
  a: {
    b: {
      c: string;
    };
    d: number;
  };
}
 
type ReadonlyNested = DeepReadonly<Nested>;
// {
//   readonly a: {
//     readonly b: {
//       readonly c: string;
//     };
//     readonly d: number;
//   };
// }
// 字符串字面量拼接
type EventName<T extends string> = `on${Capitalize<T>}`;
 
type ClickEvent = EventName<"click">;  // "onClick"
type HoverEvent = EventName<"hover">;  // "onHover"
 
// 组合多个类型
type Position = "top" | "bottom" | "left" | "right";
type Margin = `margin${Capitalize<Position>}`;
// "marginTop" | "marginBottom" | "marginLeft" | "marginRight"
 
// 嵌套模板
type HttpMethod = "get" | "post" | "put" | "delete";
type Endpoint = `/api/${string}`;
type ApiUrl = `${Uppercase<HttpMethod>} ${Endpoint}`;
// 提取事件名
type GetEvent<T extends string> = 
  T extends `on${infer Event}` ? Uncapitalize<Event> : never;
 
type E1 = GetEvent<"onClick">;  // "click"
type E2 = GetEvent<"onMouseEnter">;  // "mouseEnter"
type E3 = GetEvent<"foo">;  // never
 
// 提取 CSS 变量
type ExtractVar<T extends string> = 
  T extends `var(--${infer Var})` ? Var : never;
 
type V1 = ExtractVar<"var(--primary-color)">;  // "primary-color"
type V2 = ExtractVar<"var(--spacing-md)">;    // "spacing-md"
 
// 复杂模式匹配
type ParseRoute<T extends string> = 
  T extends `${infer Method} /api/${infer Path}` 
    ? { method: Method; path: Path }
    : never;
 
type Route = ParseRoute<"GET /api/users">;
// { method: "GET"; path: "users"; }
// 提取对象的可选属性名
type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
 
interface User {
  id: number;
  name: string;
  email?: string;
  age?: number;
}
 
type UserOptionalKeys = OptionalKeys<User>;  // "email" | "age"
 
// 提取对象的可选属性
type OptionalProps<T> = Pick<T, OptionalKeys<T>>;
type UserOptional = OptionalProps<User>;  // { email?: string; age?: number; }
 
// 扁平化嵌套对象(只支持一级)
type Flatten<T> = {
  [K in keyof T]: T[K];
};
// 路径字符串生成
type Path<T, K extends keyof T = keyof T> = 
  K extends string 
    ? T[K] extends Record<string, any>
      ? `${K}.${Path<T[K]>}` | K
      : K
    : never;
 
interface Data {
  user: {
    name: string;
    address: {
      city: string;
      country: string;
    };
  };
  settings: {
    theme: string;
  };
}
 
type DataPaths = Path<Data>;
// "user" | "user.name" | "user.address" | "user.address.city" | ...

本章我们学习了:

1. keyof - 获取类型的所有键

2. typeof - 获取值的类型

3. in - 遍历联合类型,创建映射类型

4. is - 自定义类型保护函数

5. 条件类型 - 根据类型关系选择类型

6. infer - 在条件类型中推断类型

7. 映射类型 - 转换现有类型的属性

8. 模板字面量类型 - 字符串类型的模式操作

实现一个函数,能够安全地获取对象的多个属性:

function pluck<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  // 实现
}
 
interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}
 
const product: Product = {
  id: 1,
  name: "Laptop",
  price: 999,
  category: "Electronics"
};
 
// 期望:提取 id 和 name
const summary = pluck(product, ["id", "name"]);
// 类型: { id: number; name: string; }

参考答案

function pluck<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>;
  keys.forEach(key => {
    result[key] = obj[key];
  });
  return result;
}
 
// 或者使用 reduce
function pluck2<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  return keys.reduce((acc, key) => {
    acc[key] = obj[key];
    return acc;
  }, {} as Pick<T, K>);
}
 
// 使用
const summary = pluck(product, ["id", "name"]);
console.log(summary);  // { id: 1, name: "Laptop" }

实现以下类型和类型保护函数:

interface Square {
  kind: "square";
  size: number;
}
 
interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}
 
interface Circle {
  kind: "circle";
  radius: number;
}
 
type Shape = Square | Rectangle | Circle;
 
// 实现类型保护函数和计算面积函数

参考答案

// 类型保护函数
function isSquare(shape: Shape): shape is Square {
  return shape.kind === "square";
}
 
function isRectangle(shape: Shape): shape is Rectangle {
  return shape.kind === "rectangle";
}
 
function isCircle(shape: Shape): shape is Circle {
  return shape.kind === "circle";
}
 
// 计算面积
function calculateArea(shape: Shape): number {
  if (isSquare(shape)) {
    return shape.size ** 2;
  } else if (isRectangle(shape)) {
    return shape.width * shape.height;
  } else if (isCircle(shape)) {
    return Math.PI * shape.radius ** 2;
  }
  // 穷尽检查
  const _exhaustive: never = shape;
  return _exhaustive;
}
 
// 使用
const shapes: Shape[] = [
  { kind: "square", size: 5 },
  { kind: "rectangle", width: 4, height: 6 },
  { kind: "circle", radius: 3 }
];
 
shapes.forEach(shape => {
  console.log(`${shape.kind} area:`, calculateArea(shape));
});

实现以下类型工具:

// 1. 提取数组的嵌套层级深度
type ArrayDepth<T> = any;
 
// 2. 提取函数的第 N 个参数类型
type NthParameter<T, N extends number> = any;
 
// 3. 将联合类型转换为交叉类型
type UnionToIntersection<T> = any;

参考答案

// 1. 数组嵌套深度
type ArrayDepth<T, Depth extends number[] = []> = 
  T extends (infer E)[] 
    ? ArrayDepth<E, [...Depth, 1]>
    : Depth["length"];
 
type D1 = ArrayDepth<number>;       // 0
type D2 = ArrayDepth<number[]>;     // 1
type D3 = ArrayDepth<number[][]>;   // 2
 
// 2. 第 N 个参数
type NthParameter<T, N extends number> = 
  T extends (...args: infer P) => any 
    ? P[N] 
    : never;
 
function example(a: string, b: number, c: boolean) {}
type P1 = NthParameter<typeof example, 0>;  // string
type P2 = NthParameter<typeof example, 1>;  // number
 
// 3. 联合转交叉
type UnionToIntersection<T> = 
  (T extends any ? (x: T) => void : never) extends 
  (x: infer R) => void ? R : never;
 
type U2I = UnionToIntersection<{ a: 1 } | { b: 2 }>;
// { a: 1 } & { b: 2 }

实现一个类型安全的表单验证系统:

// 要求:
// 1. 根据数据模型自动生成验证规则类型
// 2. 支持必填、最小长度、正则等验证规则
// 3. 验证函数返回详细的错误信息

参考答案

// 验证规则类型
interface ValidationRule<T> {
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  pattern?: RegExp;
  validator?: (value: T) => string | undefined;
}
 
// 字段验证器
type FieldValidators<T> = {
  [K in keyof T]?: ValidationRule<T[K]>;
};
 
// 验证错误
type ValidationErrors<T> = {
  [K in keyof T]?: string;
};
 
// 验证器类
class Validator<T extends Record<string, any>> {
  constructor(private rules: FieldValidators<T>) {}
 
  validate(data: T): { valid: boolean; errors: ValidationErrors<T> } {
    const errors: ValidationErrors<T> = {};
 
    for (const key in this.rules) {
      const rule = this.rules[key];
      const value = data[key];
 
      if (!rule) continue;
 
      // 必填检查
      if (rule.required && (value === undefined || value === null || value === "")) {
        errors[key] = `${String(key)} is required`;
        continue;
      }
 
      // 字符串验证
      if (typeof value === "string") {
        if (rule.minLength !== undefined && value.length < rule.minLength) {
          errors[key] = `${String(key)} must be at least ${rule.minLength} characters`;
          continue;
        }
 
        if (rule.maxLength !== undefined && value.length > rule.maxLength) {
          errors[key] = `${String(key)} must be at most ${rule.maxLength} characters`;
          continue;
        }
 
        if (rule.pattern && !rule.pattern.test(value)) {
          errors[key] = `${String(key)} format is invalid`;
          continue;
        }
      }
 
      // 自定义验证
      if (rule.validator) {
        const error = rule.validator(value);
        if (error) {
          errors[key] = error;
        }
      }
    }
 
    return {
      valid: Object.keys(errors).length === 0,
      errors
    };
  }
}
 
// 使用示例
interface UserForm {
  username: string;
  email: string;
  age: number;
}
 
const userValidator = new Validator<UserForm>({
  username: {
    required: true,
    minLength: 3,
    maxLength: 20
  },
  email: {
    required: true,
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
  },
  age: {
    required: true,
    validator: (value) => value < 18 ? "Must be at least 18" : undefined
  }
});
 
// 验证
const result1 = userValidator.validate({
  username: "ab",
  email: "invalid-email",
  age: 16
});
// result1.valid = false
// result1.errors = { username: "...", email: "...", age: "..." }
 
const result2 = userValidator.validate({
  username: "alice",
  email: "alice@example.com",
  age: 25
});
// result2.valid = true

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • typescript/第十四章_类型操作符.txt
  • 最后更改: 2026/03/09 15:26
  • 张叶安