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/