TypeScript类型体操实战:条件类型推导与映射类型工程化方案

TypeScript的高级类型系统允许开发者在类型层面做逻辑运算,条件类型和映射类型是其中最强大的两个特性。掌握这些能力后,可以从类型层面消除大量运行时检查代码,让编译器在构建阶段捕获更多错误。

条件类型基础与类型推导

条件类型的语法是T extends U ? X : Y,形式上类似三元运算符,但运算对象是类型而非值。infer关键字在条件类型中声明类型变量,从被检查的类型中提取子类型。

// 从Promise中提取结果类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnwrapPromise<Promise<string>>;        // string
type B = UnwrapPromise<Promise<number[]>>;      // number[]
type C = UnwrapPromise<string>;                  // string

// 从数组中提取元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;

type D = ElementOf<string[]>;    // string
type E = ElementOf<number[]>;    // number

// 从函数签名中提取返回值类型和参数类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ParamType<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;

type F = ReturnType<typeof fetch>;           // Promise<Response>
type G = ParamType<(id: number, name: string) => void>;  // number

映射类型与Key重映射

映射类型遍历一个联合类型,对每个成员应用变换,产生新的对象类型。TypeScript 4.1引入的Key Remapping语法([K in keyof T as NewKey])让映射类型更加灵活。

// 将所有属性变为可选
type Optional<T> = {
  [K in keyof T]?: T[K];
};

// 将所有属性变为只读
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

// 过滤指定类型的属性
type PickByValueType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  active: boolean;
}

// 只保留string类型属性
type StringProps = PickByValueType<User, string>;
// { name: string; email: string }

// 只保留number类型属性
type NumberProps = PickByValueType<User, number>;
// { id: number; age: number }

// 将属性名转为大写
type UppercaseKeys<T> = {
  [K in keyof T as Uppercase<string & K>]: T[K];
};

type UpperUser = UppercaseKeys<{ name: string; age: number }>;
// { NAME: string; AGE: number }

模板字面量类型与路径映射

模板字面量类型允许在类型层面拼接字符串,配合infer可以实现类型安全的路径解析和事件名推导。

// API路由路径类型提取
type ExtractParams<Path extends string> =
  Path extends `${infer _Start}:${infer Param}/${infer Rest}`
    ? { [K in Param]: string } & ExtractParams<`/${Rest}`>
    : Path extends `${infer _Start}:${infer Param}`
    ? { [K in Param]: string }
    : {};

type Route1 = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }

type Route2 = ExtractParams<"/api/v1/products/:productId">;
// { productId: string }

// 事件监听器类型映射
type EventName<T extends string> = `on${Capitalize<T>}`;

type ClickEvent = EventName<"click">;     // "onClick"
type FocusEvent = EventName<"focus">;     // "onFocus"

// 为事件名生成对应的handler类型
type Handlers<Events extends string> = {
  [E in Events as EventName<E>]?: (event: E) => void;
};

type ButtonHandlers = Handlers<"click" | "focus" | "blur">;
// { onClick?: ...; onFocus?: ...; onBlur?: ... }

分布式条件类型

当条件类型的左侧是泛型类型参数且传入联合类型时,条件类型会分布执行——对联合类型的每个成员分别求值,再合并结果。

// 分布式行为:ToUnion接受联合类型时逐个处理
type ToArray<T> = T extends any ? T[] : never;

type H = ToArray<string | number>;
// string[] | number[] (不是 (string | number)[])

// 阻止分布:用方括号包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type I = ToArrayNonDist<string | number>;
// (string | number)[]

// 实际应用:排除null和undefined
type NonNullable<T> = T extends null | undefined ? never : T;

type J = NonNullable<string | null | number | undefined>;
// string | number

实战:类型安全的API客户端

将上述类型工具组合起来,构建一个类型安全的API请求函数,让参数和返回值都由路由定义自动推导:

// API路由定义
interface ApiRoutes {
  "GET /users": { params: {}; query: { page?: number }; response: User[] };
  "GET /users/:id": { params: { id: string }; query: {}; response: User };
  "POST /users": { params: {}; body: { name: string; email: string }; response: User };
  "DELETE /users/:id": { params: { id: string }; query: {}; response: void };
}

// 从路由定义中提取路径和HTTP方法
type RouteKey = keyof ApiRoutes;
type ExtractMethod<K extends RouteKey> = K extends `${infer M} ${string}` ? M : never;
type ExtractPath<K extends RouteKey> = K extends `${string} ${infer P}` ? P : never;

// 类型安全的request函数
async function request<K extends RouteKey>(
  route: K,
  options: {
    params?: ExtractParams<ExtractPath<K>>;
    query?: ApiRoutes[K]["query"] & Record<string, any>;
    body?: "body" extends keyof ApiRoutes[K] ? ApiRoutes[K]["body"] : never;
  }
): Promise<ApiRoutes[K]["response"]> {
  const [method, path] = route.split(" ") as [ExtractMethod<K>, ExtractPath<K>];
  // 实际请求逻辑...
  return {} as any;
}

// 使用:类型完全推导
const users = await request("GET /users", { query: { page: 1 } });
// users: User[]

const user = await request("GET /users/:id", { params: { id: "123" } });
// user: User

await request("POST /users", { body: { name: "test", email: "a@b.com" } });
// body类型由路由定义约束

这种模式的优势在于:路由定义是唯一的类型源,request函数的参数和返回值类型全部自动推导,任何路由变更在编译阶段就能被TypeScript捕获。不需要维护单独的类型声明文件,也不需要手动标注每个API函数的参数类型。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-lei-xing-ti-cao-shi-zhan-tiao-jian-lei-xing-tui/

赞 (0)
小编小编
上一篇 2026年9月18日
下一篇 2026年9月18日

相关推荐

TypeScript类型体操实战:条件类型推断与模板字面量类型高级用法

TypeScript条件类型与infer关键字深度解析

TypeScript的条件类型(Conditional Types)是类型系统中最强大的编程能力之一,语法为T extends U ? X : Y,在类型层面实现分支判断。当T可赋值给U时返回类型X,否则返回类型Y。

条件类型真正强大之处在于配合infer关键字做类型推断。infer允许在条件类型的extends子句中声明一个类型变量,由TypeScript编译器自动推断该位置应匹配的类型。

// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

// 提取Promise内部类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;

// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;

// 提取对象值的类型联合
type ValueOf<T> = T extends { [k: string]: infer V } ? V : never;

infer可在同一条件类型中多次使用,也可在分布式条件类型中使用。分布式条件类型是指当条件类型作用于联合类型时,会自动将联合类型的每个成员分别代入计算,再将结果合并为联合类型。

模板字面量类型与字符串模式匹配

TypeScript 4.1引入的模板字面量类型(Template Literal Types)为字符串类型的操作提供了强大的编程能力。配合内置字符串操作类型Uppercase、Lowercase、Capitalize、Uncapitalize,可在类型层面实现字符串变换。

// CSS属性名转CSS变量名
type CSSVarName<T extends string> = `--${Uncapitalize<T>}`;

type MarginVar = CSSVarName<"Margin">; // "--margin"

// 事件名转换:onChange -> ChangeCallback
type CallbackName<T extends string> = 
  T extends `on${infer Rest}` ? `${Rest}Callback` : never;

type X = CallbackName<"onChange">; // "ChangeCallback"
type Y = CallbackName<"onClick">; // "ClickCallback"

模板字面量类型的递归使用可以实现更复杂的字符串解析,如从URL路径中提取参数类型:

// 提取路由参数类型
type ExtractRouteParams<T extends string> = 
  T extends `${infer _Head}:${infer Param}/${infer Rest}`
    ? { [K in Param | keyof ExtractRouteParams<Rest>]: string }
    : T extends `${infer _Head}:${infer Param}`
    ? { [K in Param]: string }
    : {};

type Params = ExtractRouteParams<"/users/:id/posts/:postId">;
// { id: string; postId: string }

映射类型与Key Remapping高级技巧

TypeScript 4.1同时引入了键重映射(Key Remapping)语法,允许在映射类型中通过as子句变换键名,结合模板字面量类型可以批量重命名对象属性:

// 将对象所有属性变为Getter函数
type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

interface User {
  name: string;
  age: number;
}

type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number }

键重映射还可以用于过滤属性,结合条件类型实现Pick/Omit的变体:

// 只保留函数类型的属性
type PickFunctions<T> = {
  [K in keyof T as T[K] extends Function ? K : never]: T[K];
};

// 排除undefined和null类型的属性
type NonNullableKeys<T> = {
  [K in keyof T as null extends T[K] ? never : undefined extends T[K] ? never : K]: T[K];
};

递归类型与深层Readonly实现

TypeScript的类型系统支持递归类型定义,可以实现深层类型变换。DeepReadonly是典型的递归条件类型:

type DeepReadonly<T> = T extends Function
  ? T
  : T extends object
  ? { readonly [K in keyof T]: DeepReadonly<T[K]> }
  : T;

type DeepPartial<T> = T extends object
  ? { [K in keyof T]?: DeepPartial<T[K]> }
  : T;

type DeepRequired<T> = T extends object
  ? { [K in keyof T]-?: DeepRequired<T[K]> }
  : T;

// 构建嵌套路径类型(用于安全访问深层属性)
type PathKeys<T> = T extends object
  ? { [K in keyof T & string]: T[K] extends object
      ? K | `${K}.${PathKeys<T[K]>}`
      : K
  }[keyof T & string]
  : never;

interface Config {
  database: { host: string; port: number; auth: { user: string; pass: string } };
  cache: { ttl: number; prefix: string };
}

type ConfigPaths = PathKeys<Config>;
// "database" | "database.host" | "database.port" | "database.auth" | "database.auth.user" | "database.auth.pass" | "cache" | "cache.ttl" | "cache.prefix"

递归类型需注意TypeScript的递归深度限制,默认情况下类型实例化深度上限约50层,超过会报”Type produces a tuple type that is too large to represent”错误。实际使用中,3-5层的递归深度足以覆盖绝大多数业务场景。

原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-lei-xing-ti-cao-shi-zhan-tiao-jian-lei-xing-tui/

赞 (0)
小编小编
上一篇 2026年8月18日
下一篇 2026年8月18日

相关推荐