TypeScript的类型系统具备图灵完备的计算能力,通过条件类型、映射类型和infer关键字,可以在编译期完成复杂的类型变换。前端工程化实践中,高级类型技巧广泛用于组件库设计中提取Props类型、API接口返回值类型自动推导、表单类型校验等场景。TypeScript实战中掌握类型体操不仅是写更安全的代码,更是构建可维护的类型抽象层的基础。
条件类型基础与类型分发机制
条件类型的语法形式为T extends U ? X : Y,在编译期根据泛型参数的约束关系选择不同的类型分支。当T是联合类型时,条件类型会自动分发,对联合类型的每个成员分别求值。
// 基础条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false
type C = IsString<string | number>; // true | false => boolean
// 条件类型分发机制
type ToArray<T> = T extends any ? T[] : never;
type D = ToArray<string | number>; // string[] | number[]
// 阻止分发:用方括号包裹
type ToArrayAll<T> = [T] extends [any] ? T[] : never;
type E = ToArrayAll<string | number>; // (string | number)[]
infer关键字与类型变量捕获
infer在条件类型的extends子句中声明类型变量,用于捕获待推导的类型片段。这是类型体操中最核心的工具,能够从函数签名、数组、Promise等类型中提取内部结构。
// 提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type F1 = ReturnType<() => string>; // string
type F2 = ReturnType<(x: number) => boolean>; // boolean
// 提取函数第一个参数类型
type FirstParam<T> = T extends (first: infer P, ...rest: any[]) => any ? P : never;
type P1 = FirstParam<(id: number, name: string) => void>; // number
// 提取Promise的值类型
type Awaited<T> = T extends Promise<infer U> ? U : T;
type R1 = Awaited<Promise<string>>; // string
type R2 = Awaited<Promise<Promise<number>>>; // number
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type Item = ElementOf<string[]>; // string
// 提取对象属性值类型
type ValueOf<T> = T extends object ? T[keyof T] : never;
type V = ValueOf<{ a: string; b: number }>; // string | number
映射类型与键重映射
映射类型基于已有类型生成新类型,语法形式为{ [K in keyof T]: NewType }。TypeScript 4.1引入的键重映射允许在映射过程中修改键名,通过as子句实现。
// Partial: 所有属性变为可选
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// Required: 所有属性变为必选
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
// 键重映射:将所有键转为大写
type UpperKeys<T> = {
[K in keyof T as Uppercase<string & K>]: T[K];
};
type Config = { host: string; port: number };
type UpperConfig = UpperKeys<Config>;
// { HOST: string; PORT: number }
// 键重映射:过滤特定键
type RemoveKindField<T> = {
[K in keyof T as Exclude<K, "kind">]: T[K];
};
type Circle = { kind: "circle"; radius: number };
type CircleData = RemoveKindField<Circle>;
// { radius: number }
// 键重映射:添加前缀
type AddPrefix<T, P extends string> = {
[K in keyof T as `${P}${Capitalize<string & K>}`]: T[K];
};
type ApiResponse = AddPrefix<{ id: number; name: string }, "api">;
// { apiId: number; apiName: string }
泛型约束与类型守卫结合
泛型约束使用extends关键字限制泛型参数必须满足的结构。结合类型守卫,可以在运行时安全地收窄类型范围,实现编译期与运行时的双重保障。
// 泛型约束:要求T必须有length属性
function getLength<T extends { length: number }>(arg: T): number {
return arg.length;
}
getLength("hello"); // 5
getLength([1, 2, 3]); // 3
// 条件类型约束
type PropertyType<T, K extends string> =
T extends { [P in K]: infer V } ? V : undefined;
type R = PropertyType<{ name: string; age: number }, "name">; // string
type R2 = PropertyType<{ name: string }, "email">; // undefined
// 类型守卫函数
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every(v => typeof v === "string");
}
function processArray(arr: unknown) {
if (isStringArray(arr)) {
return arr.map(s => s.toUpperCase());
}
return [];
}
// 自定义类型谓词与泛型结合
function assertProperty<T extends object, K extends string>(
obj: T, key: K
): obj is T & { [P in K]: unknown } {
return key in obj;
}
模板字面量类型与路由参数推导
模板字面量类型允许将字符串字面量类型通过${}插值组合成新类型。这是前端框架类型体操的高阶应用,可以用于路由参数类型推导、事件名映射等场景。
// 路由参数提取
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 Route1 = ExtractParams<"/users/:userId">;
// { userId: string }
type Route2 = ExtractParams<"/posts/:postId/comments/:commentId">;
// { postId: string; commentId: string }
// 类型安全的路由构建器
function createRouter<T extends string>(routes: Record<T, () => void>) {
return routes;
}
const router = createRouter({
"/": () => console.log("home"),
"/users/:id": () => console.log("user detail"),
"/posts/:postId/edit": () => console.log("edit post")
});
// 事件名称映射
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
// 从事件名称提取原始事件名
type OriginalEvent<T> = T extends `on${infer E}` ? Uncapitalize<E> : never;
type Original = OriginalEvent<"onClick">; // "click"
组件Props类型自动推导实战
在React组件开发中,利用条件类型和映射类型可以自动推导组件的Props类型,实现受控/非受控组件的联合Props类型。
import React from "react";
type ValueType = string | number | boolean;
// 受控/非受控组件Props推导
type PropsWithControl<T extends ValueType> = {
value: T;
onChange: (value: T) => void;
defaultValue?: never;
} | {
value?: never;
onChange?: (value: T) => void;
defaultValue?: T;
};
function useControllableState<T extends ValueType>(
props: PropsWithControl<T>
): [T, (value: T) => void] {
const [internalValue, setInternalValue] = React.useState<T>(
(props as any).defaultValue ?? (props as any).value
);
const isControlled = "value" in props && props.value !== undefined;
const currentValue = isControlled ? props.value! : internalValue;
const setValue = (value: T) => {
if (!isControlled) {
setInternalValue(value);
}
props.onChange?.(value);
};
return [currentValue, setValue];
}
// 编译器自动检查value/onChange的配对关系
// <Input value="hello" onChange={...} /> // 正确
// <Input value="hello" defaultValue="hi" /> // 编译错误
// <Input defaultValue="hi" /> // 正确(非受控)
TypeScript高级类型系统的价值在于编译期的类型安全保障。条件类型和infer关键字提供了类型级别的模式匹配能力,映射类型配合键重映射可以实现类型结构变换。模板字面量类型将字符串操作引入类型层,使得路由解析、事件映射等常见前端模式获得了类型推导支持。在实际项目中,这些技巧的适度使用能够显著提升代码的可维护性和类型安全性,但需要注意类型复杂度与可读性之间的平衡。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-gao-ji-lei-xing-shi-zhan-tiao-jian-lei-xing-tui/