TS高级类型编程
# 05.TS高级类型编程
映射类型、模板字面量类型、递归类型——掌握"类型体操"的核心技巧,让类型为你生成类型。
# 1. 案例引入
# 1.1 烦人的重复定义
// 产品接口——创建时需要所有字段
interface Product {
id: number;
name: string;
price: number;
description: string;
}
// 更新产品时,所有字段都可选
// 又写一遍?万一原始类型有字段增删……
interface ProductUpdate {
id?: number;
name?: string;
price?: number;
description?: string;
}
# 1.2 类型编程的解法
// 一行搞定——永远不会与原始定义脱节
type ProductUpdate = Partial<Product>;
// 自动包含了所有字段的可选版本
// 当 Product 新增 stock 字段时,ProductUpdate 自动跟上
这就是高级类型编程的魅力:不是"一个类型写一个类型",而是用类型运算符从已有类型推导出新类型。
# 2. 索引访问类型
# 2.1 基础语法:T[K]
interface User {
name: string;
age: number;
tags: string[];
}
type NameType = User["name"]; // string
type TagsType = User["tags"]; // string[]
// 联合访问——同时取多个属性
type NameOrAge = User["name" | "age"]; // string | number
# 2.2 配合 keyof 和泛型
// 安全的属性类型提取
type PropType<T, K extends keyof T> = T[K];
type U1 = PropType<User, "name">; // string
type U2 = PropType<User, "age">; // number
// type U3 = PropType<User, "email">; // ❌
// 访问所有属性的类型
type AllTypes = User[keyof User]; // string | number | string[]
# 2.3 数组元素的索引访问
type Arr = string[];
type ElementType = Arr[number]; // string
// 结合泛型——获取任意数组的元素类型
type ElementOf<T> = T extends (infer E)[] ? E : T[number];
type E1 = ElementOf<number[]>; // number
type E2 = ElementOf<User[]>; // User
# 3. 映射类型(Mapped Types)
# 3.1 核心语法
// 语法:{ [K in 联合类型]: 新类型 }
// 遍历每个 key,生成新的属性类型
// 将 T 的所有属性变为只读
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
// 使用
type ReadonlyUser = MyReadonly<User>;
// { readonly name: string; readonly age: number; readonly tags: string[] }
# 3.2 映射的三种遍历方式
// 1. 遍历 keyof T——保留所有属性
type Clone<T> = { [K in keyof T]: T[K] };
// 2. 遍历固定联合类型——创建指定属性
type Tags = { [K in "title" | "author"]: string };
// { title: string; author: string }
// 3. 结合 as 重映射(TypeScript 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getName: () => string; getAge: () => number; getTags: () => string[] }
# 3.3 映射修饰符
// + 和 - 控制 readonly 和 ?
type Mutable<T> = {
-readonly [K in keyof T]: T[K]; // 去掉 readonly
};
type Required<T> = {
[K in keyof T]-?: T[K]; // 去掉 ?(强制必填)
};
// 练习:组合使用
type PartialReadonly<T> = {
readonly [K in keyof T]?: T[K]; // 全部只读 + 可选
};
# 4. 模板字面量类型(Template Literal Types)
# 4.1 字符串类型的"模板引擎"
// TypeScript 4.1+ 新特性
type Greeting = `Hello, ${string}`;
let g1: Greeting = "Hello, World"; // ✅
// let g2: Greeting = "Hi, World"; // ❌
// 与字面量联合类型组合——真正的"类型计算"
type EventName = "click" | "hover" | "focus";
type ElementName = "Button" | "Input";
type ComponentEvent = `${ElementName}${Capitalize<EventName>}`;
// "ButtonClick" | "ButtonHover" | "ButtonFocus"
// | "InputClick" | "InputHover" | "InputFocus" ——6种组合自动生成
# 4.2 内置字符串工具类型
// TS 内置四个字符串操作类型
type T1 = Uppercase<"hello">; // "HELLO"
type T2 = Lowercase<"HELLO">; // "hello"
type T3 = Capitalize<"hello">; // "Hello"
type T4 = Uncapitalize<"Hello">; // "hello"
# 4.3 实战:API 路径类型系统
// 为 REST API 生成类型安全的路径
type Resource = "user" | "post" | "comment";
type Method = "get" | "create" | "update" | "delete";
type APIPath = `/${Resource}/${Method}`;
// 自动生成:
// "/user/get" | "/user/create" | "/user/update" | "/user/delete"
// | "/post/get" | "/post/create" | ...
// 进一步约束
type APIPathWithID = `/${Resource}/${string}/${Method}`;
let path: APIPathWithID = "/user/123/update"; // ✅
// let path2: APIPathWithID = "/user/get"; // ❌ 缺少 ID
# 5. 递归类型
# 5.1 为什么需要递归类型
// 需求:深层嵌套的对象全部 readonly
interface Nested {
name: string;
settings: {
theme: string;
notifications: {
email: boolean;
push: boolean;
};
};
}
// 普通映射类型只到第一层
type Readonly<T> = { readonly [K in keyof T]: T[K] };
type R = Readonly<Nested>;
// R.settings.notifications.push = false; // ✅ 内层不是 readonly!
# 5.2 递归映射——深只读
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K] // 函数不需要深层只读
: DeepReadonly<T[K]> // 递归处理嵌套对象
: T[K];
};
type DeepR = DeepReadonly<Nested>;
// DeepR.settings.notifications.push = false; // ❌ 深层也被 readonly 了
# 5.3 递归类型的更多应用
// 深 Partial
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object
? DeepPartial<T[K]>
: T[K];
};
// 平展联合数组
type Flatten<T> = T extends (infer E)[] ? E : T;
type Flat = Flatten<string[][]>; // string[] — 只平展了一层
// 递归平展
type DeepFlatten<T> = T extends (infer E)[] ? DeepFlatten<E> : T;
type DeepFlat = DeepFlatten<string[][][]>; // string
# 6. 内置工具类型深度拆解
# 6.1 Partial / Required / Readonly
// TypeScript 标准库实现
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P]; // -? 移除可选
};
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
# 6.2 Pick / Omit
// Pick:从 T 中挑出指定属性
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type NameOnly = Pick<User, "name">; // { name: string }
// Omit:从 T 中移除指定属性(组合 Exclude + Pick)
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type WithoutAge = Omit<User, "age">; // { name: string; tags: string[] }
type WithoutNameAndAge = Omit<User, "name" | "age">; // { tags: string[] }
# 6.3 Record
// Record:创建键值类型映射
type Record<K extends keyof any, V> = {
[P in K]: V;
};
// 统一值类型
type PageInfo = Record<"home" | "about" | "contact", { title: string }>;
// { home: {title:string}; about: {title:string}; contact: {title:string} }
// 字典模式
type StringMap = Record<string, unknown>;
# 6.4 Exclude / Extract / NonNullable
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type NonNullable<T> = T extends null | undefined ? never : T;
type T1 = Exclude<"a" | "b" | "c", "b" | "c">; // "a"
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
type T3 = NonNullable<string | null | undefined>; // string
# 6.5 ReturnType / Parameters / ConstructorParameters
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type ConstructorParameters<T extends new (...args: any) => any> =
T extends new (...args: infer P) => any ? P : never;
# 7. 实战:类型安全的表单校验系统
// 步骤 1:定义表单数据类型
interface LoginForm {
username: string;
password: string;
remember: boolean;
}
// 步骤 2:为表单生成校验规则类型
type ValidationRule<T> = {
[K in keyof T]: (value: T[K]) => string | null;
};
// 步骤 3:定义校验器(每个字段类型不同,校验也不同)
const loginValidator: ValidationRule<LoginForm> = {
username: (v: string) => v.length >= 3 ? null : "用户名至少3个字符",
password: (v: string) => v.length >= 6 ? null : "密码至少6个字符",
// remember: (v: boolean) => null // 可选
};
// 步骤 4:错误信息生成——Partial + Record
type FormErrors<T> = Partial<Record<keyof T, string>>;
function validate<T>(data: T, rules: Partial<ValidationRule<T>>): FormErrors<T> {
const errors: FormErrors<T> = {};
for (const key in rules) {
const rule = rules[key];
if (rule) {
const error = rule(data[key]);
if (error) errors[key] = error;
}
}
return errors;
}
// 使用——全类型安全
const errors = validate({ username: "ab", password: "1234", remember: true }, loginValidator);
// errors: { username?: string; password?: string; remember?: string }
# 8. 速查表
| 类型操作 | 语法 | 效果 |
|---|---|---|
| 索引访问 | T["key"] | 提取属性类型 |
| 遍历映射 | { [K in T]: V } | 为联合每个成员生成属性 |
| 带 as 映射 | { [K in T as New]: V } | 重命名 key |
| 只读 | +readonly / -readonly | 加/去 readonly |
| 可选 | +? / -? | 加/去 ? |
| 模板字面量 | `前${T}后` | 拼接字符串类型 |
| 条件 | T extends U ? X : Y | 分支类型 |
| 递归 | DeepReadonly<T> = ... | 自引用类型 |
| Partial | [P in keyof T]?: T[P] | 全部可选 |
| Pick | Pick<T, K> | 选择属性子集 |
| Omit | Omit<T, K> | 排除指定属性 |
| Record | Record<K, V> | 键值对映射 |
| ReturnType | ReturnType<Fn> | 取函数返回值 |
下一篇:06.TS类型守卫机制
上次更新: 2026/06/24, 12:59:24