TypeScript类型体操与模式匹配
TypeScript的类型系统是前端开发中最强大的特性之一,它不仅能提供编译时的类型安全,还能在类型层面进行编程操作。掌握类型体操(TypeScript metaprogramming)能让你写出更加优雅、类型安全的代码。
在本文中,我将深入探讨TypeScript的高级类型特性,包括条件类型、映射类型、模板字面量类型等,并结合实际应用场景展示如何使用这些特性构建类型安全的解决方案。
1. TypeScript类型系统基础
1.1 类型层次结构
let num: number = 42; let str: string = "hello"; let bool: boolean = true; let arr: number[] = [1, 2, 3]; let obj: { name: string; age: number } = { name: "John", age: 25 };
type ID = string | number;
type Person = { name: string; } & { age: number; };
type Point = { x: number; y: number; };
interface User { id: number; name: string; email: string; }
function identity<T>(value: T): T { return value; }
interface HasLength { length: number; }
function logLength<T extends HasLength>(value: T): T { console.log(value.length); return value; }
|
1.2 类型守卫与类型收窄
function isString(value: unknown): value is string { return typeof value === 'string'; }
function processValue(value: unknown) { if (isString(value)) { console.log(value.toUpperCase()); } else { console.log('Not a string'); } }
function process(value: number | string) { if (typeof value === 'string') { console.log(value.length); } else { console.log(value.toFixed(2)); } }
class Animal { name: string; constructor(name: string) { this.name = name; } }
class Dog extends Animal { bark() { console.log('Woof!'); } }
function makeSound(animal: Animal) { if (animal instanceof Dog) { animal.bark(); } else { console.log('Generic animal sound'); } }
|
1.3 类型推断与类型断言
let inferred = "hello";
function add(a: number, b: number) { return a + b; }
const input = document.getElementById('my-input') as HTMLInputElement;
const value = input!.value;
const unknownValue: unknown = "hello"; const strValue = unknownValue as string;
const obj = { x: 1, y: 2 } as const;
function createPoint(x: number, y: number): [number, number] { return [x, y] as const; }
|
2. 高级类型特性
2.1 条件类型
type ExtractType<T, U> = T extends U ? T : never;
type StringOnly = ExtractType<string | number, string>;
type ExcludeType<T, U> = T extends U ? never : T;
type NumbersOnly = ExcludeType<string | number, string>;
type NonNullable<T> = T extends null | undefined ? never : T;
type SafeString = NonNullable<string | null>;
type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
type Func = (a: number, b: number) => string; type FuncReturn = ReturnType<Func>;
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type FuncParams = Parameters<Func>;
type ConstructorParameters<T> = T extends new (...args: infer P) => any ? P : never;
class MyClass { constructor(name: string, age: number) {} }
type ClassParams = ConstructorParameters<typeof MyClass>;
|
2.2 映射类型
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
type ReadonlyUser = Readonly<User>;
type Partial<T> = { [P in keyof T]?: T[P]; };
type PartialUser = Partial<User>;
type Required<T> = { [P in keyof T]-?: T[P]; };
type RequiredUser = Required<PartialUser>;
type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
type UserName = Pick<User, 'name'>;
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type UserWithoutEmail = Omit<User, 'email'>;
type Mutable<T> = { -readonly [P in keyof T]: T[P]; };
type MutableUser = Mutable<ReadonlyUser>;
type Nullable<T> = { [P in keyof T]: T[P] | null; };
type NullableUser = Nullable<User>;
type StringifyKeys<T> = { [K in keyof T as `${string & K}`]: T[K]; };
type StringifiedUser = StringifyKeys<User>;
|
2.3 模板字面量类型
type Greeting = `Hello ${string}`;
type FormattedName = `Mr. ${string}` | `Ms. ${string}` | `Mrs. ${string}`;
type Color = 'red' | 'green' | 'blue';
type CSSClass = `text-${Color}`;
type CapitalizeString<S extends string> = S extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Lowercase<Rest>}` : S;
type UserName = 'john doe'; type Capitalized = CapitalizeString<UserName>;
type CamelCase<S extends string> = S extends `${infer First}_${infer Rest}` ? `${First}${CapitalizeString<Rest>}` : S;
type SnakeCase = 'user_name'; type CamelCased = CamelCase<SnakeCase>;
type ExtractPath<T extends string> = T extends `${infer Dir}/${infer File}` ? { directory: Dir; file: File } : { path: T };
type PathInfo = ExtractPath<'src/components/Button'>;
type ApiEndpoint = `api/${string}/${number}`;
type ValidEndpoint = ApiEndpoint; type InvalidEndpoint = `api/${string}${string}`;
|
2.4 索引访问与递归类型
type NestedObject = { user: { name: string; address: { street: string; city: string; }; }; };
type UserName = NestedObject['user']['name']; type UserAddress = NestedObject['user']['address'];
type DeepPick<T, Paths extends string[]> = Paths extends [infer First, ...infer Rest] ? First extends keyof T ? { [K in First]: DeepPick<T[First], Rest> } : never : T;
type PickedUser = DeepPick<NestedObject, ['user', 'name']>;
type Flatten<T> = T extends (infer U)[] ? U : T extends object ? { [K in keyof T]: Flatten<T[K]> } : T;
type NestedArray = number[][][]; type FlatArray = Flatten<NestedArray>;
type NestedObjectWithArrays = { numbers: number[][]; strings: string[][]; }; type FlattenedObject = Flatten<NestedObjectWithArrays>;
type DeepPartial<T> = { [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]; };
type DeepPartialUser = DeepPartial<NestedObject>;
|
3. 类型体操实战
3.1 实现类型级别的工具函数
type Capitalize<T extends string> = T extends `${infer First}${infer Rest}` ? `${Uppercase<First>}${Rest}` : T;
type UserName = 'john'; type CapitalizedName = Capitalize<UserName>;
type Uncapitalize<T extends string> = T extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${Rest}` : T;
type ClassName = 'MyComponent'; type LowerCaseClass = Uncapitalize<ClassName>;
type KebabCase<T extends string> = T extends `${infer First}${infer Rest}` ? `${Lowercase<First>}${KebabCase<Rest>}` : T;
type ClassName2 = 'MyComponentName'; type KebabName = KebabCase<ClassName2>;
type Split<S extends string, D extends string> = S extends `${infer First}${D}${infer Rest}` ? [First, ...Split<Rest, D>] : [S];
type PathParts = Split<'src/components/Button', '/'>;
type Length<T extends any[]> = T extends readonly any[] ? T['length'] : never;
type Numbers = [1, 2, 3, 4, 5]; type Count = Length<Numbers>;
type Push<T extends any[], E> = [...T, E]; type Pop<T extends any[]> = T extends [...infer Rest, infer _] ? Rest : never;
type NewArray = Push<Numbers, 6>; type ShorterArray = Pop<Numbers>;
type Unique<T extends any[]> = T extends [infer First, ...infer Rest] ? First extends Rest[number] ? Unique<Rest> : [First, ...Unique<Rest>] : [];
type DuplicateArray = [1, 2, 2, 3, 4, 4, 5]; type UniqueArray = Unique<DuplicateArray>;
|
3.2 模式匹配实现
type MatchResult<T, Cases> = T extends Cases ? true : false;
type IsString = MatchResult<string, string>; type IsNumber = MatchResult<string, number>;
type Match<T, Cases, Default = never> = T extends Cases ? T : Default;
type StringOrNumber = Match<string | number, string>; type NeitherStringNorNumber = Match<boolean, string | number>;
type ConditionalMatch<T, Condition, Cases, Default = never> = T extends Condition ? Cases : Default;
type PositiveNumber = ConditionalMatch<number, number, 'number', 'not number'>; type NotPositiveNumber = ConditionalMatch<string, number, 'number', 'not number'>;
type Switch<T, Cases> = T extends keyof Cases ? Cases[T] : never;
type Cases = { 'on': string; 'off': boolean; 'toggle': () => void; };
type OnAction = Switch<'on', Cases>; type ToggleAction = Switch<'toggle', Cases>; type DefaultAction = Switch<'unknown', Cases>;
type DeepSearch<T, SearchType, Result = never> = T extends SearchType ? T : T extends object ? { [K in keyof T]: DeepSearch<T[K], SearchType> }[keyof T] : Result;
type MixedType = { a: string; b: number; c: { d: string; e: boolean; }; };
type StringValues = DeepSearch<MixedType, string>;
type Map<T, Mapper> = T extends [infer First, ...infer Rest] ? [Mapper<First>, ...Map<Rest, Mapper>] : [];
type Strings = ['hello', 'world']; type Mapped = Map<Strings, (s: string) => number>;
|
3.3 实际应用场景
type ApiResponse<T> = { data: T; status: number; message: string; };
type UserResponse = ApiResponse<{ id: number; name: string; email: string; }>;
type ValidationRules<T> = { [K in keyof T]: (value: T[K]) => string | null; };
type UserValidation = ValidationRules<{ name: string; age: number; email: string; }>;
type StateMachine<State extends string, Event extends string, Transitions> = { currentState: State; transition: (event: Event) => { newState: State; action?: () => void; }; };
type SimpleMachine = StateMachine< 'idle' | 'loading' | 'success' | 'error', 'start' | 'success' | 'error' | 'retry', { idle: { start: { newState: 'loading'; action: () => void }; }; loading: { success: { newState: 'success'; action: () => void }; error: { newState: 'error'; action: () => void }; }; error: { retry: { newState: 'loading'; action: () => void }; }; } >;
type Computed<T, Computations> = T & { [K in keyof Computations]: Computations[K] extends (data: T) => infer R ? R : never; };
type Person = { firstName: string; lastName: string; age: number; };
type PersonWithComputed = Computed<Person, { fullName: (person: Person) => string; isAdult: (person: Person) => boolean; }>;
type DynamicKeys<T, Prefix extends string> = { [K in keyof T as `${Prefix}${Capitalize<string & K}`]: T[K]; };
type UserData = { id: number; name: string; };
type PrefixedData = DynamicKeys<UserData, 'user'>;
|
4. 类型级编程技巧
4.1 类型级别的条件逻辑
type If<C, T, F> = C extends true ? T : F;
type IsTrue = If<true, string, number>; type IsFalse = If<false, string, number>;
type Times<T, N, Acc extends any[] = []> = Acc['length'] extends N ? Acc : Times<T, N, [...Acc, T]>;
type FiveNumbers = Times<number, 3>;
type While<Condition, T, Body> = Condition<T> extends true ? While<Condition, Body<T>, Body> : T;
type CountUp<T extends number, N extends number, Acc extends number = 0> = Acc extends N ? Acc : CountUp<T, N, Acc extends number ? Acc + 1 : never>;
type Ten = CountUp<never, 10>;
|
4.2 类型级别的数学运算
type Add<A extends number, B extends number> = [...Times<any, A>, ...Times<any, B>]['length'];
type Sum = Add<2, 3>;
type Subtract<A extends number, B extends number> = A extends B ? 0 : Times<any, A, [...Times<any, B>]>['length'];
type Difference = Subtract<5, 3>;
type Multiply<A extends number, B extends number> = Times<any, A, Times<any, B>>['length'];
type Product = Multiply<3, 4>;
type Divide<D extends number, N extends number, Acc extends number = 0> = N extends 0 ? never : Times<any, D> extends [...infer Rest, ...Times<any, N>] ? Divide<D, Rest['length'], Acc + 1> : Acc;
type Quotient = Divide<10, 3>;
|
4.3 类型级别的字符串操作
type Reverse<S extends string, Acc extends string = ''> = S extends `${infer First}${infer Rest}` ? Reverse<Rest, `${First}${Acc}`> : Acc;
type Reversed = Reverse<'hello'>;
type Find<S extends string, Search extends string, Acc extends string = ''> = S extends `${infer First}${infer Rest}` ? Search extends `${First}${Acc}` ? true : Find<Rest, Search, `${Acc}${First}`> : false;
type Found = Find<'hello world', 'world'>; type NotFound = Find<'hello world', 'goodbye'>;
type Replace<S extends string, Search extends string, Replace extends string, Acc extends string = ''> = S extends `${infer First}${infer Rest}` ? S extends `${Search}${infer Suffix}` ? `${Acc}${Replace}${Suffix}` : Replace<Rest, Search, Replace, `${Acc}${First}`> : `${Acc}${S}`;
type Replaced = Replace<'hello world', 'world', 'typescript'>;
|
5. 高级模式匹配模式
5.1 解构模式
type ArrayDestructure<T extends any[], Pattern extends any[]> = T extends [infer First, ...infer Rest] ? Pattern extends [infer FirstPattern, ...infer RestPattern] ? FirstPattern extends '_' ? { [K in keyof RestPattern]: Rest[K] } : { [K in keyof RestPattern]: Rest[K] extends FirstPattern ? Rest[K] : never; } : never;
type Point = [number, number, number]; type PointDestructure = ArrayDestructure<Point, ['_', '_', 'x']>;
type ObjectDestructure<T, Pattern> = T extends object ? { [K in keyof Pattern]: K extends keyof T ? T[K] : never } : never;
type User = { name: string; age: number; email: string; };
type UserDestructure = ObjectDestructure<User, { name: string; age: number }>;
|
5.2 守卫模式
type Guard<T, Condition> = T extends Condition ? true : false;
type IsPositive = Guard<number, number>;
type CompositeGuard<T, Conditions extends any[]> = Conditions extends [infer First, ...infer Rest] ? First extends T ? CompositeGuard<T, Rest> : false : true;
type IsStringAndNumber = CompositeGuard<number, [number, string]>; type IsNumberAndNumber = CompositeGuard<number, [number, number]>;
type GuardBranch<T, Guards, Default = never> = T extends Guards[keyof Guards] ? Guards[T] : Default;
type Guards = { string: 'string'; number: 'number'; boolean: 'boolean'; };
type BranchResult = GuardBranch<string, Guards>; type BranchDefault = GuardBranch<null, Guards>;
|
5.3 递归模式匹配
type Tree<T> = { value: T; children?: Tree<T>[]; };
type TreePattern<T, Matcher> = T extends Tree<infer V> ? Matcher extends (value: V, children: Tree<V>[]) => infer R ? R : never : never;
type SumTree = Tree<number>; type SumMatcher = (value: number, children: Tree<number>[]) => number;
type SumResult = TreePattern<SumTree, SumMatcher>;
type DepthFirst<T, Visitor> = T extends Tree<infer V> ? Visitor extends (value: V, children: T[]) => infer R ? R : never : never;
type Visitor = (value: number, children: SumTree[]) => string;
type DFSResult = DepthFirst<SumTree, Visitor>;
|
6. 实际应用案例
6.1 表单类型安全
type FormField<T, R extends (value: T) => string | null> = { value: T; error: string | null; validate: R; };
type FormState<T extends Record<string, any>> = { [K in keyof T]: FormField<T[K], (value: T[K]) => string | null>; };
type FormBuilder<T extends Record<string, any>> = { fields: { [K in keyof T]: { value: T[K]; validator: (value: T[K]) => string | null; }; }; };
type BuildForm<T> = T extends FormBuilder<infer Fields> ? FormState<Fields> : never;
type UserForm = { name: string; age: number; email: string; };
type ValidatedForm = BuildForm<{ name: { value: string; validator: (name: string) => string | null }; age: { value: number; validator: (age: number) => string | null }; email: { value: string; validator: (email: string) => string | null }; }>;
|
6.2 Redux状态管理
type Action<T extends string, P = void> = { type: T; payload: P; };
type Reducer<S, A extends Action<any, any>> = (state: S, action: A) => S;
type Store<S, A extends Action<any, any>> = { dispatch: (action: A) => void; getState: () => S; subscribe: (listener: () => void) => () => void; };
type ActionCreators<T extends Record<string, (...args: any[]) => Action<any>>> = { [K in keyof T]: T[K] extends (...args: infer Args) => infer A ? (...args: Args) => A : never; };
type UserAction = | Action<'SET_USER', { name: string; age: number }> | Action<'CLEAR_USER'>;
type UserReducer = Reducer<{ user: { name: string; age: number } | null; }, UserAction>;
type UserActionCreators = ActionCreators<{ setUser: (name: string, age: number) => Action<'SET_USER', { name: string; age: number }>; clearUser: () => Action<'CLEAR_USER'>; }>;
|
6.3 API调用类型安全
type ApiResponse<T> = Promise<{ data: T; status: number; message: string; }>;
type ApiClient = { get: <T>(url: string) => ApiResponse<T>; post: <T>(url: string, data: any) => ApiResponse<T>; put: <T>(url: string, data: any) => ApiResponse<T>; delete: <T>(url: string) => ApiResponse<T>; };
type ApiRoutes = { users: { list: () => ApiResponse<User[]>; get: (id: string) => ApiResponse<User>; create: (user: Omit<User, 'id'>) => ApiResponse<User>; update: (id: string, user: Partial<User>) => ApiResponse<User>; delete: (id: string) => ApiResponse<void>; }; posts: { list: () => ApiResponse<Post[]>; get: (id: string) => ApiResponse<Post>; create: (post: Omit<Post, 'id'>) => ApiResponse<Post>; }; };
type CreateApiClient<T> = { [K in keyof T]: { [P in keyof T[K]]: T[K][P] extends (...args: infer Args) => infer R ? (...args: Args) => R : never; }; };
type Client = CreateApiClient<ApiRoutes>;
|
7. 性能优化与最佳实践
7.1 类型性能优化
type Optimized<T> = T extends object ? { [K in keyof T]: Optimized<T[K]> } : T;
type LimitedDepth<T, Depth extends number> = Depth extends 0 ? T : T extends object ? { [K in keyof T]: LimitedDepth<T[K], Depth extends number ? Depth - 1 : never> } : T;
type DeepObject = { level1: { level2: { level3: string; }; }; };
type LimitedDeep = LimitedDepth<DeepObject, 2>;
type InferFrom<T> = T extends (infer U)[] ? U : T;
type StringArray = string[]; type ElementType = InferFrom<StringArray>;
|
7.2 类型系统最佳实践
type Strict<T, U> = T extends U ? U : never;
type StrictString = Strict<string | number, string>;
type ExcludeUndefined<T> = T extends undefined ? never : T;
type NonUndefinedValues = ExcludeUndefined<string | number | undefined>;
function isDefined<T>(value: T | undefined): value is T { return value !== undefined; }
function isType<T>(value: any, type: string): value is T { return typeof value === type; }
|
7.3 调试类型
type Print<T> = T extends infer U ? U : never;
type Debug = Print<string>;
type Check<T, Name extends string = 'Type'> = T extends infer U ? { [K in Name]: U } : never;
type Checked = Check<number>;
type DeepCheck<T, Depth extends number = 3> = Depth extends 0 ? Check<T> : T extends object ? { [K in keyof T]: DeepCheck<T[K], Depth extends number ? Depth - 1 : never> } : Check<T>;
|
8. 总结与展望
8.1 类型体操的核心概念
- 条件类型:基于条件选择不同的类型
- 映射类型:在类型级别进行转换
- 模板字面量类型:操作字符串类型
- 递归类型:处理复杂的数据结构
- 模式匹配:实现类型级别的逻辑分支
8.2 实际应用价值
- 类型安全:确保代码在编译时类型正确
- 代码质量:通过类型约束提高代码质量
- 文档化:类型作为代码的文档
- IDE支持:获得更好的IDE智能提示
- 重构安全:类型检查确保重构的安全性
8.3 未来发展方向
- 更好的类型推断:编译器能够更准确地推断复杂类型
- 运行时类型检查:类型信息的运行时验证
- 模块化类型系统:更好的类型模块化支持
- 类型级别的编程能力:更强大的类型级计算能力
9. 结语
TypeScript的类型系统是一个强大的工具,它不仅提供了编译时的类型安全,还让我们能够在类型层面进行编程。通过掌握类型体操和模式匹配,你可以编写出更加优雅、类型安全的代码。
记住,类型系统的目的是帮助开发者写出更好的代码,而不是限制开发者的创造力。合理使用类型特性,在保证类型安全的同时保持代码的简洁和可读性。
希望本文能够帮助你更好地理解和使用TypeScript的高级类型特性。如果你有任何问题或建议,欢迎在评论区交流分享!
本文由笔者根据实际项目经验总结,如有疏漏之处,敬请指正。