TypeScript实战:类型体操与泛型高级用法详解

TypeScript的类型系统具备图灵完备性,通过条件类型、映射类型和模板字面量类型的组合,可以在编译期完成复杂的类型计算。本文从实际工程场景出发,拆解类型体操的核心模式与泛型高级用法。

泛型约束与条件类型基础

泛型约束(Generic Constraints)通过extends关键字限制类型参数的范围,是类型体操的基石。条件类型(Conditional Types)则让类型可以根据输入类型动态分支:

// 泛型约束:要求T必须有length属性
function getLength(arg: T): number {
  return arg.length;
}

// 条件类型:根据输入类型返回不同类型
type IsString = T extends string ? true : false;

type A = IsString<"hello">;  // true
type B = IsString<42>;       // false

// distributive conditional types(分布式条件类型)
type ExcludeNull = T extends null | undefined ? never : T;
type C = ExcludeNull;  // string

infer关键字在条件类型中提取子类型,是解构复杂类型的利器:

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

// 提取Promise的泛型参数
type Unpacked = T extends Promise ? U : T;
type D = Unpacked>;  // number

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

// 提取构造函数的实例类型
type InstanceType any> =
  T extends new (...args: any[]) => infer R ? R : never;

映射类型与键重映射实战

映射类型(Mapped Types)遍历已有类型的属性生成新类型,配合键重映射(Key Remapping)实现属性名的动态变换:

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

// 将所有属性变为可选
type Partial = {
  [P in keyof T]?: T[P];
};

// 键重映射:将属性名转为大写
type UpperCaseKeys = {
  [P in keyof T as Uppercase]: T[P];
};

interface User {
  name: string;
  age: number;
}
type UpperUser = UpperCaseKeys;
// { NAME: string; AGE: number }

// 过滤特定类型的属性
type PickByValue = {
  [P in keyof T as T[P] extends ValueType ? P : never]: T[P];
};

interface APIResponse {
  id: number;
  name: string;
  active: boolean;
  score: number;
}
type NumberFields = PickByValue;
// { id: number; score: number }

模板字面量类型与路径推导

模板字面量类型(Template Literal Types)在字符串类型层面做拼接和变换,前端工程化中常用于路由推导和CSS属性类型安全:

// CSS属性类型生成
type CSSProperty = `${string}-${string}`;
type MarginProperty = `margin${"" | "Top" | "Right" | "Bottom" | "Left"}`;
// "margin" | "marginTop" | "marginRight" | "marginBottom" | "marginLeft"

// 事件处理器类型推导
type EventName = `on${Capitalize}`;
type ClickEvent = EventName<"click">;  // "onClick"

// 路由参数提取
type ExtractParams =
  Route extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param]: string } & ExtractParams<`/${Rest}`>
    : Route extends `${string}:${infer Param}`
    ? { [K in Param]: string }
    : {};

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

// 实际应用:类型安全的路由跳转
function navigate(
  path: Route,
  params: ExtractParams
): void {
  // 实现路由跳转逻辑
}

// 类型检查通过
navigate("/users/:userId/posts/:postId", {
  userId: "123",
  postId: "456"
});
// 类型检查失败 - 缺少postId
// navigate("/users/:userId/posts/:postId", { userId: "123" });

泛型工具类型深度实现

结合上述模式,实现工程中常用的工具类型:

// DeepPartial:递归可选
type DeepPartial = {
  [P in keyof T]?: T[P] extends object ? DeepPartial : T[P];
};

// DeepReadonly:递归只读
type DeepReadonly = {
  readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P];
};

// Mutable:移除只读
type Mutable = {
  -readonly [P in keyof T]: T[P];
};

// Get:按路径获取嵌套属性类型
type Get =
  Path extends `${infer Key}.${infer Rest}`
    ? Key extends keyof T
      ? Get
      : never
    : Path extends keyof T
    ? T[Path]
    : never;

interface State {
  user: {
    profile: {
      name: string;
      avatar: string;
    };
    settings: {
      theme: "dark" | "light";
    };
  };
}

type UserName = Get;  // string
type Theme = Get;   // "dark" | "light"

// Paths:生成所有属性路径
type Paths = {
  [K in keyof T & string]: T[K] extends object
    ? Paths
    : `${P}${P extends "" ? "" : "."}${K}`
}[keyof T & string];

type StatePaths = Paths;
// "user.profile.name" | "user.profile.avatar" | "user.settings.theme"

组件Props类型推导实战

在Vue3和React组件开发中,泛型类型推导能大幅提升类型安全性:

// React:泛型组件 - Table组件列定义类型推导
interface Column {
  key: keyof T;
  title: string;
  render?: (value: T[keyof T], record: T) => React.ReactNode;
}

function Table(props: {
  data: T[];
  columns: Column[];
}) {
  // 渲染逻辑
}

// 使用时类型自动推导
interface User {
  id: number;
  name: string;
  email: string;
}

const userColumns: Column[] = [
  { key: "id", title: "ID" },
  { key: "name", title: "姓名" },
  {
    key: "email",
    title: "邮箱",
    render: (value) => {value}
  }
];

// 类型检查通过
Table({ data: users, columns: userColumns });
// 类型检查失败 - key必须是User的属性
// Table({ data: users, columns: [{ key: "phone", title: "手机" }] });

// Vue3:defineComponent泛型推导
import { defineComponent, PropType } from "vue";

export default defineComponent({
  props: {
    data: {
      type: Array as PropType,
      required: true
    },
    columns: {
      type: Array as PropType[]>,
      required: true
    }
  },
  setup(props) {
    // props.data 类型为 User[]
    // props.columns 类型为 Column[]
  }
});

类型体操的核心思路是”先写使用方式,再推导类型实现”。遇到复杂类型问题时,先明确期望的类型表达式,再逐步用条件类型、映射类型和infer拆解。TypeScript 5.0引入的const泛型参数进一步简化了字面量类型的推导,减少as const的冗余写法。

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

(0)
小编小编
上一篇 12小时前
下一篇 12小时前

相关推荐