TypeScript高级类型实战指南 前言 TypeScript已经成为现代前端开发的标配,但很多开发者只是把它当作”带类型的JavaScript”来使用,停留在基础类型注解的层面。实际上,TypeScript的类型系统非常强大,可以实现复杂的类型推导和类型转换。本文将带你深入TypeScript的高级类型世界,掌握类型编程的精髓。
一、泛型:类型的参数化 1.1 基础泛型 泛型允许我们在定义函数、类或接口时,不预先指定具体的类型,而是在使用时再指定。
1 2 3 4 5 6 7 8 9 10 11 function identity<T>(arg : T): T { return arg; } const result1 = identity<string >("hello" );const result2 = identity<number >(42 );const result3 = identity ("world" );
1.2 泛型约束 使用extends关键字约束泛型的范围:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Lengthwise { length : number ; } function logLength<T extends Lengthwise >(arg : T): T { console .log (arg.length ); return arg; } logLength ("hello" ); logLength ([1 , 2 , 3 ]); logLength ({ length : 10 });
1.3 泛型工具函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function getFirst<T>(arr : T[]): T | undefined { return arr[0 ]; } function getProperty<T, K extends keyof T>(obj : T, key : K): T[K] { return obj[key]; } const person = { name : "张三" , age : 25 };const name = getProperty (person, "name" ); const age = getProperty (person, "age" );
二、条件类型:类型的if-else 2.1 基础条件类型 条件类型的语法:T extends U ? X : Y
1 2 3 4 5 6 7 8 9 10 11 type IsString <T> = T extends string ? true : false ;type A = IsString <string >; type B = IsString <number >; type UnwrapPromise <T> = T extends Promise <infer U> ? U : T;type C = UnwrapPromise <Promise <string >>; type D = UnwrapPromise <number >;
2.2 分布式条件类型 当条件类型作用于联合类型时,会自动分发:
1 2 3 4 5 6 7 8 9 10 11 type ToArray <T> = T extends any ? T[] : never ;type E = ToArray <string | number >;type NonNullable <T> = T extends null | undefined ? never : T;type F = NonNullable <string | null | undefined >;
2.3 infer关键字 infer用于在条件类型中推断类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type ReturnType <T> = T extends (...args : any []) => infer R ? R : never ;function greet ( ): string { return "Hello" ; } type G = ReturnType <typeof greet>; type Parameters <T> = T extends (...args : infer P) => any ? P : never ;function add (a : number , b : number ): number { return a + b; } type H = Parameters <typeof add>; type ArrayElement <T> = T extends (infer E)[] ? E : never ;type I = ArrayElement <string []>;
三、映射类型:类型的转换 3.1 基础映射类型 映射类型可以将一个类型转换为另一个类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type Partial <T> = { [P in keyof T]?: T[P]; }; interface User { name : string ; age : number ; } type PartialUser = Partial <User >;type Required <T> = { [P in keyof T]-?: T[P]; }; type Readonly <T> = { readonly [P in keyof T]: T[P]; };
3.2 键名映射 TypeScript 4.1+ 支持键名映射:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type AddPrefix <T, Prefix extends string > = { [K in keyof T as `${Prefix} ${string & K} ` ]: T[K]; }; type User = { name : string ; age : number ; }; type PrefixedUser = AddPrefix <User , "user_" >;type FilterByType <T, U> = { [K in keyof T as T[K] extends U ? K : never ]: T[K]; }; type Person = { name : string ; age : number ; email : string ; }; type StringProps = FilterByType <Person , string >;
3.3 实战:构建类型安全的表单 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 type FormField <T> = { value : T; error ?: string ; touched : boolean ; }; type FormState <T> = { [K in keyof T]: FormField <T[K]>; }; interface RegisterForm { username : string ; password : string ; age : number ; } type RegisterFormState = FormState <RegisterForm >;const formState : RegisterFormState = { username : { value : "" , touched : false }, password : { value : "" , touched : false , error : "密码不能为空" }, age : { value : 0 , touched : false }, };
四、模板字面量类型 4.1 基础用法 TypeScript 4.1引入了模板字面量类型:
1 2 3 4 5 6 7 8 9 type World = "world" ;type Greeting = `hello ${World} ` ; type Method = "GET" | "POST" ;type Path = "/users" | "/posts" ;type ApiEndpoint = `${Method} ${Path} ` ;
4.2 实战:CSS in JS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type CSSProperty = | "color" | "backgroundColor" | "fontSize" | "margin" | "padding" ; type WithPx <T extends string > = `${T} Px` ;type WithEm <T extends string > = `${T} Em` ;type SizeProperty = "fontSize" | "margin" | "padding" ;type CSSPropertyWithUnit = | CSSProperty | WithPx <SizeProperty > | WithEm <SizeProperty >; type StyleObject = { [K in CSSPropertyWithUnit ]?: string | number ; }; const style : StyleObject = { color : "red" , fontSizePx : 16 , marginEm : 1.5 , };
4.3 字符串操作 TypeScript提供了内置的字符串操作类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 type Uppercase <S extends string > = intrinsic;type A = Uppercase <"hello" >; type Lowercase <S extends string > = intrinsic;type B = Lowercase <"HELLO" >; type Capitalize <S extends string > = intrinsic;type C = Capitalize <"hello" >; type Uncapitalize <S extends string > = intrinsic;type D = Uncapitalize <"Hello" >; type Getters <T> = { [K in keyof T as `get${Capitalize<string & K>} ` ]: () => T[K]; }; type Setters <T> = { [K in keyof T as `set${Capitalize<string & K>} ` ]: (value : T[K] ) => void ; }; interface State { name : string ; age : number ; } type StateGetters = Getters <State >;type StateSetters = Setters <State >;
五、高级类型实战案例 5.1 类型安全的Redux 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 type Action <T extends string , P = void > = P extends void ? { type : T } : { type : T; payload : P }; function createAction<T extends string , P = void >(type : T) { return (payload : P ) => ({ type , payload }) as Action <T, P>; } const increment = createAction<"INCREMENT" , number >("INCREMENT" );const decrement = createAction<"DECREMENT" >("DECREMENT" );type Actions = | ReturnType <typeof increment> | ReturnType <typeof decrement>; function reducer (state : number = 0 , action : Actions ): number { switch (action.type ) { case "INCREMENT" : return state + action.payload ; case "DECREMENT" : return state - 1 ; default : return state; } }
5.2 类型安全的路由 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 type RouteConfig = { "/" : {}; "/users" : {}; "/users/:id" : { id : string }; "/posts/:postId/comments/:commentId" : { postId : string ; commentId : string ; }; }; type ExtractParams <T extends string > = T extends `${infer _Start} :${infer Param} /${infer Rest} ` ? { [K in Param | keyof ExtractParams <Rest >]: string } : T extends `${infer _Start} :${infer Param} ` ? { [K in Param ]: string } : {}; function navigate<K extends keyof RouteConfig >( path : K, ...params : RouteConfig [K] extends {} ? [RouteConfig [K]] : [] ): void { console .log ("Navigate to:" , path, params[0 ]); } navigate ("/" ); navigate ("/users" ); navigate ("/users/:id" , { id : "123" }); navigate ("/posts/:postId/comments/:commentId" , { postId : "1" , commentId : "2" });
5.3 深度只读类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 type DeepReadonly <T> = { readonly [K in keyof T]: T[K] extends object ? T[K] extends Function ? T[K] : DeepReadonly <T[K]> : T[K]; }; interface Config { database : { host : string ; port : number ; credentials : { username : string ; password : string ; }; }; } type ReadonlyConfig = DeepReadonly <Config >;const config : ReadonlyConfig = { database : { host : "localhost" , port : 5432 , credentials : { username : "admin" , password : "secret" , }, }, };
六、类型体操技巧 6.1 元组操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type First <T extends any []> = T extends [infer F, ...any []] ? F : never ;type A = First <[1 , 2 , 3 ]>; type Last <T extends any []> = T extends [...any [], infer L] ? L : never ;type B = Last <[1 , 2 , 3 ]>; type Shift <T extends any []> = T extends [any , ...infer Rest ] ? Rest : [];type C = Shift <[1 , 2 , 3 ]>; type Reverse <T extends any []> = T extends [infer F, ...infer Rest ] ? [...Reverse <Rest >, F] : []; type D = Reverse <[1 , 2 , 3 ]>;
6.2 联合类型操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type UnionToIntersection <U> = ( U extends any ? (x : U ) => void : never ) extends (x : infer I) => void ? I : never ; type E = UnionToIntersection <{ a : string } | { b : number }>;type Exclude <T, U> = T extends U ? never : T;type F = Exclude <"a" | "b" | "c" , "a" >; type Extract <T, U> = T extends U ? T : never ;type G = Extract <"a" | "b" | "c" , "a" | "b" >;
七、性能与最佳实践 7.1 避免过度复杂的类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Complex <T> = T extends { a : infer A } ? A extends { b : infer B } ? B extends { c : infer C } ? C : never : never : never ; type GetA <T> = T extends { a : infer A } ? A : never ;type GetB <T> = T extends { b : infer B } ? B : never ;type GetC <T> = T extends { c : infer C } ? C : never ;type Better <T> = GetC <GetB <GetA <T>>>;
7.2 使用类型断言时要谨慎 1 2 3 4 5 6 7 8 9 10 11 12 const user = { name : "张三" } as any ;user.age .toString (); function isUser (obj : any ): obj is User { return typeof obj.name === "string" && typeof obj.age === "number" ; } if (isUser (user)) { user.age .toString (); }
7.3 合理使用工具类型 TypeScript内置了很多实用的工具类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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] };type Pick <T, K extends keyof T> = { [P in K]: T[P] };type Omit <T, K extends keyof any > = Pick <T, Exclude <keyof T, K>>;type Record <K extends keyof any , T> = { [P in K]: T };
八、总结 TypeScript的高级类型系统为我们提供了强大的类型编程能力,掌握这些技巧可以:
提升代码质量 :编译期捕获更多错误
改善开发体验 :更好的IDE提示和自动补全
增强代码可维护性 :类型即文档
实现类型安全 :避免运行时类型错误
学习建议
从基础泛型开始,逐步深入
多看优秀库的类型定义(如React、Vue)
善用TypeScript Playground调试类型
关注TypeScript版本更新,学习新特性
掌握TypeScript高级类型,让你的代码更加健壮和优雅!
参考资料