TypeScript 高级类型与实战技巧 2026

🔷 类型是最好的文档。 TypeScript 的高级类型系统是其最强大的特性之一。掌握条件类型、映射类型、模板字面量类型等"类型体操"技巧,可以让你在编译阶段就消除大量 Bug,写出更健壮的代码。本文带你从进阶到精通 TypeScript 类型系统。
本文将带你系统掌握:
- ✅ 条件类型与分布式条件类型
- ✅ 映射类型与键重映射
- ✅ 模板字面量类型
- ✅ infer 关键字与类型提取
- ✅ 类型守卫与类型收窄
- ✅ 实用类型体操技巧
- ✅ 类型编程实战案例
- ✅ 类型性能优化
一、条件类型
1.1 基本语法
条件类型就像类型层面的三元运算符:
T extends U ? X : Y示例:
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
type C = IsString<string>; // true1.2 实用条件类型
// 排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
// 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 提取 Promise 返回类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type R1 = ElementOf<string[]>; // string
type R2 = ElementOf<number[]>; // number
type R3 = Awaited<Promise<boolean>>; // boolean1.3 分布式条件类型
当 T 是联合类型时,条件类型会分布式地应用于每个成员:
type ToArray<T> = T extends any ? T[] : never;
// 分布式:联合类型会被拆开
type R = ToArray<string | number>;
// string[] | number[]
// 阻止分布:用方括号包裹
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type R2 = ToArrayNonDist<string | number>;
// (string | number)[]实战:排除类型
type Exclude<T, U> = T extends U ? never : T;
type Extract<T, U> = T extends U ? T : never;
type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"二、infer 关键字
2.1 基本用法
infer 用于在条件类型中声明并推断类型变量:
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R = GetReturnType<() => string>; // string2.2 提取函数参数类型
// 获取第一个参数类型
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
// 获取所有参数类型
type Params<T> = T extends (...args: infer P) => any ? P : never;
type P1 = FirstParam<(name: string, age: number) => void>; // string
type P2 = Params<(name: string, age: number) => void>; // [string, number]2.3 提取构造函数类型
type InstanceType<T> = T extends new (...args: any[]) => infer I ? I : never;
class Person {}
type P = InstanceType<typeof Person>; // Person2.4 提取数组首尾元素
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type F = First<[1, 2, 3]>; // 1
type L = Last<[1, 2, 3]>; // 32.5 提取 Promise 链
// 递归解包嵌套 Promise
type UnwrapPromise<T> = T extends Promise<infer U>
? U extends Promise<any>
? UnwrapPromise<U>
: U
: T;
type R = UnwrapPromise<Promise<Promise<Promise<number>>>>; // number三、映射类型
3.1 基本映射
映射类型可以基于旧类型创建新类型:
// 所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
// 所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 所有属性变为可空
type Nullable<T> = {
[P in keyof T]: T[P] | null;
};3.2 键重映射(Key Remapping)
TypeScript 4.1+ 支持在映射类型中重新映射键:
// 将所有键添加前缀
type AddPrefix<T, Prefix extends string> = {
[K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K];
};
interface User {
name: string;
age: number;
}
type PrefixedUser = AddPrefix<User, "user">;
// { userName: string; userAge: number }3.3 过滤属性
// 只保留函数属性
type FunctionProperties<T> = {
[K in keyof T as T[K] extends Function ? K : never]: T[K];
};
// 只保留非函数属性
type NonFunctionProperties<T> = {
[K in keyof T as T[K] extends Function ? never : K]: T[K];
};
class MyClass {
name = "test";
age = 18;
greet() {}
}
type Data = NonFunctionProperties<MyClass>; // { name: string; age: number }3.4 修改属性修饰符
// 移除 readonly
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// 移除可选
type Required<T> = {
[P in keyof T]-?: T[P];
};四、模板字面量类型
4.1 基本用法
type Greeting = `hello ${string}`;
const g1: Greeting = "hello world"; // OK
const g2: Greeting = "hello typescript"; // OK4.2 联合类型组合
type Color = "red" | "blue" | "green";
type Size = "small" | "medium" | "large";
type Style = `${Size}-${Color}`;
// "small-red" | "small-blue" | "small-green" | "medium-red" | ...4.3 方法名生成
type Fetcher = {
[K in keyof API as `fetch${Capitalize<string & K>}`]: () => Promise<API[K]>;
};
interface API {
user: User;
post: Post;
}
// 生成: fetchUser(): Promise<User> 和 fetchPost(): Promise<Post>4.4 路径类型
type Path<T, P extends string = ""> =
T extends object
? {
[K in keyof T & string]: Path<T[K], `${P}.${K}`>;
}[keyof T & string]
: P;
interface Config {
db: { host: string; port: number };
redis: { url: string };
}
type ConfigPaths = Path<Config>;
// "db.host" | "db.port" | "redis.url"五、类型守卫
5.1 typeof 守卫
function process(value: string | number) {
if (typeof value === "string") {
value.toUpperCase(); // 类型收窄为 string
} else {
value.toFixed(2); // 类型收窄为 number
}
}5.2 instanceof 守卫
class NetworkError extends Error {
constructor(public statusCode: number) {
super();
}
}
function handleError(err: Error | NetworkError) {
if (err instanceof NetworkError) {
console.log(err.statusCode); // 类型收窄为 NetworkError
}
}5.3 in 守卫
interface Fish { swim: () => void }
interface Bird { fly: () => void }
function move(animal: Fish | Bird) {
if ("swim" in animal) {
animal.swim(); // 类型收窄为 Fish
} else {
animal.fly(); // 类型收窄为 Bird
}
}5.4 自定义类型守卫
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(v => typeof v === "string");
}
function isError(value: unknown): value is Error {
return value instanceof Error;
}
// 使用
function handle(data: unknown) {
if (isStringArray(data)) {
data.forEach(s => s.toUpperCase()); // 类型为 string[]
}
}5.5 断言函数
function assertDefined<T>(value: T | undefined | null): asserts value is T {
if (value === undefined || value === null) {
throw new Error(`Expected value to be defined, got ${value}`);
}
}
function processUser(user: User | undefined) {
assertDefined(user);
user.name; // 类型收窄为 User
}六、实用类型体操
6.1 DeepPartial
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Config {
db: { host: string; port: number };
cache: { ttl: number };
}
type PartialConfig = DeepPartial<Config>;
// { db?: { host?: string; port?: number }; cache?: { ttl?: number } }6.2 DeepReadonly
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};6.3 Omit 与 Pick 组合
// 按类型选取属性
type PickByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};
interface User {
name: string;
age: number;
email: string;
active: boolean;
}
type StringProps = PickByType<User, string>;
// { name: string; email: string }
type NumberProps = PickByType<User, number>;
// { age: number }6.4 联合类型转交叉类型
type UnionToIntersection<U> =
(U extends any ? (x: U) => void : never) extends (x: infer I) => void
? I
: never;
type R = UnionToIntersection<{ a: 1 } | { b: 2 }>;
// { a: 1 } & { b: 2 }6.5 获取对象路径类型
type Get<T, P extends string> =
P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? Get<T[Key], Rest>
: never
: P extends keyof T
? T[P]
: never;
interface Config {
db: { host: string; port: number };
}
type HostType = Get<Config, "db.host">; // string
type PortType = Get<Config, "db.port">; // number七、类型编程实战
7.1 类型安全的 EventEmitter
type EventMap = Record<string, any>;
class TypedEmitter<Events extends EventMap> {
private listeners = new Map<keyof Events, Set<Function>>();
on<K extends keyof Events>(event: K, fn: (payload: Events[K]) => void) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(fn);
return this;
}
emit<K extends keyof Events>(event: K, payload: Events[K]) {
this.listeners.get(event)?.forEach(fn => fn(payload));
}
off<K extends keyof Events>(event: K, fn: Function) {
this.listeners.get(event)?.delete(fn);
}
}
// 使用
interface MyEvents {
open: { url: string };
close: { code: number };
}
const emitter = new TypedEmitter<MyEvents>();
emitter.on("open", (e) => e.url); // e 类型: { url: string }
emitter.emit("close", { code: 100 }); // OK
// emitter.emit("close", { wrong: 1 }) // 编译错误!7.2 类型安全的路由参数
type ExtractParams<T extends string> =
T extends `${string}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractParams<Rest>
: T extends `${string}:${infer Param}`
? { [K in Param]: string }
: {};
// 使用
type Params = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }
function route<T extends string>(
path: T,
params: ExtractParams<T>
): string {
let result = path;
for (const [key, value] of Object.entries(params)) {
result = result.replace(`:${key}`, value);
}
return result;
}
route("/users/:userId/posts/:postId", { userId: "1", postId: "2" }); // OK
// route("/users/:userId", {}) // 编译错误!缺少 userId7.3 类型安全的状态机
type Transition<S, M> = {
[K in keyof M]: K extends keyof S ? M[K] : never;
};
interface States {
idle: { type: "idle" };
loading: { type: "loading"; requestId: string };
success: { type: "success"; data: string };
error: { type: "error"; message: string };
}
type State = States[keyof States];
// { type: "idle" } | { type: "loading"; requestId: string } | ...
type Transitions = {
idle: "loading";
loading: "success" | "error";
success: "idle";
error: "idle";
};
function transition(
current: State,
next: Transitions[State["type"]]
): State {
// 状态转换逻辑
return { type: next } as State;
}八、类型性能优化
8.1 避免过度递归
// 慢:递归深度大
type DeepPartialSlow<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartialSlow<T[P]> : T[P];
};
// 快:限制递归深度
type DeepPartial<T, Depth extends number = 3> = {
[P in keyof T]?: Depth extends 0
? T[P]
: T[P] extends object
? DeepPartial<T[P], [...Tuple<Depth>, 0]["length"]>
: T[P];
};8.2 使用 satisfies 替代直接断言
// TypeScript 4.9+
const config = {
port: 3000,
host: "localhost",
} satisfies Record<string, string | number>;
// config 推断为 { port: number; host: string },而不是 Record<string, string | number>8.3 避免大联合类型
// 不推荐:大联合类型导致编译慢
type Status = "idle" | "loading" | "success" | "error" | "timeout" | "offline" | "connecting" | "disconnected" | "paused" | "resuming";
// 推荐:使用枚举或常量对象
const Status = {
IDLE: "idle",
LOADING: "loading",
SUCCESS: "success",
ERROR: "error",
} as const;
type Status = typeof Status[keyof typeof Status];九、常用工具类型速查
// 内置工具类型
Partial<T> // 所有属性可选
Required<T> // 所有属性必填
Readonly<T> // 所有属性只读
Pick<T, K> // 选取部分属性
Omit<T, K> // 排除部分属性
Record<K, V> // 键值对类型
ReturnType<T> // 函数返回类型
Parameters<T> // 函数参数类型
InstanceType<T> // 构造函数实例类型
NonNullable<T> // 排除 null 和 undefined
Awaited<T> // 解包 Promise
Uppercase<S> // 字符串大写
Lowercase<S> // 字符串小写
Capitalize<S> // 首字母大写
Uncapitalize<S> // 首字母小写
// 常用自定义工具类型
DeepPartial<T> // 深层可选
DeepReadonly<T> // 深层只读
PickByType<T, U> // 按类型选取
Mutable<T> // 移除只读
Optional<T, K> // 指定属性可选十、总结
TypeScript 高级类型是构建大型应用的核心能力:
- 条件类型是类型层面的
if-else - infer 让你从已有类型中提取信息
- 映射类型让你变换和重塑类型
- 模板字面量类型让字符串类型编程成为可能
- 类型守卫让运行时类型检查与编译时类型安全无缝衔接
- 类型体操解决实际问题的利器,但要避免过度复杂
🔷 类型安全的代码是最好的代码。掌握 TypeScript 高级类型,让你在编译阶段就消灭 Bug。
相关文章推荐: