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)为字符串类型的操作提供了强大的编程能力。配合内置字符串操作类型UppercaseLowercaseCapitalizeUncapitalize,可在类型层面实现字符串变换。

// 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)
小编小编
上一篇 10小时前
下一篇 10小时前

相关推荐