TypeScript 高级类型 提供了强大的工具,允许开发者以更灵活、更精确的方式定义和操作类型。这些高级类型不仅增强了代码的类型安全性,还提升了开发体验,使得复杂的数据结构和业务逻辑能够更清晰地表达和维护。掌握 TypeScript 的这些高级特性,是成为一名高效 TypeScript 开发者的关键。
核心思想 :高级类型允许我们基于现有类型进行转换、组合、提取,以及根据不同条件生成新类型,从而构建出更健壮、更具表达力的类型系统。
一、联合类型 (Union Types) 联合类型表示一个值可以是多种类型中的任意一种。使用 | 符号连接不同的类型。
1.1 定义与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type StringOrNumber = string | number ;let id : StringOrNumber ;id = "123" ; id = 123 ; function printId (id : StringOrNumber ) { console .log (`Your ID is: ${id} ` ); } printId ("abc" );printId (123 );
1.2 联合类型与类型守卫 (Type Guards) 当使用联合类型时,你通常需要根据值的实际类型执行不同的操作。类型守卫 允许 TypeScript 编译器推断出变量在特定代码块内的具体类型。
常见的类型守卫方法:
typeof 类型守卫 :适用于原始类型 (string, number, boolean, symbol, bigint, undefined, function, object)。
instanceof 类型守卫 :适用于类实例。
in 操作符类型守卫 :适用于检查对象是否具有某个属性。
字面量类型守卫 (判等缩小类型)。
自定义类型守卫 (User-Defined Type Guard) 。
1.2.1 typeof 类型守卫 1 2 3 4 5 6 7 8 9 10 11 12 function printIdDetail (id : StringOrNumber ) { if (typeof id === "string" ) { console .log (id.toUpperCase ()); } else { console .log (id.toFixed (2 )); } } printIdDetail ("hello" ); printIdDetail (123.456 );
1.2.2 instanceof 类型守卫 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Dog { bark ( ) { console .log ('Woof!' ); } } class Cat { meow ( ) { console .log ('Meow!' ); } } type Pet = Dog | Cat ;function makeSound (pet : Pet ) { if (pet instanceof Dog ) { pet.bark (); } else { pet.meow (); } } makeSound (new Dog ()); makeSound (new Cat ());
1.2.3 in 操作符类型守卫 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 interface Bird { fly (): void ; layEggs (): void ; } interface Fish { swim (): void ; layEggs (): void ; } type Animal = Bird | Fish ;function move (animal : Animal ) { if ("fly" in animal) { animal.fly (); } else { animal.swim (); } } const bird : Bird = { fly : () => console .log ('flying' ), layEggs : () => {} };const fish : Fish = { swim : () => console .log ('swimming' ), layEggs : () => {} };move (bird); move (fish);
1.2.4 字面量类型守卫 (判等缩小类型) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 interface Circle { kind : "circle" ; radius : number ; } interface Square { kind : "square" ; sideLength : number ; } type Shape = Circle | Square ;function getArea (shape : Shape ): number { if (shape.kind === "circle" ) { return Math .PI * shape.radius ** 2 ; } else { return shape.sideLength ** 2 ; } } console .log (getArea ({ kind : "circle" , radius : 5 })); console .log (getArea ({ kind : "square" , sideLength : 4 }));
1.2.5 自定义类型守卫 (User-Defined Type Guard) 通过返回一个类型谓词 parameterName is Type 来告诉 TypeScript 编译器在函数返回 true 时,参数的类型是什么。
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 interface Administrator { name : string ; privileges : string []; } interface User { name : string ; startDate : Date ; } type Person = Administrator | User ;function isAdministrator (person : Person ): person is Administrator { return (person as Administrator ).privileges !== undefined ; } function processPerson (person : Person ) { if (isAdministrator (person)) { console .log (`Admin ${person.name} has privileges: ${person.privileges.join(', ' )} ` ); } else { console .log (`User ${person.name} joined on: ${person.startDate.toLocaleDateString()} ` ); } } const admin : Administrator = { name : "Max" , privileges : ["create-server" ] };const user : User = { name : "Anna" , startDate : new Date () };processPerson (admin); processPerson (user);
二、交叉类型 (Intersection Types) 交叉类型将多个类型合并为一个类型,这意味着一个值必须同时具备所有这些类型的特性。使用 & 符号连接不同的类型。
2.1 定义与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface Colorful { color : string ; } interface Printable { print (): void ; } type ColorfulPrintable = Colorful & Printable ;const obj : ColorfulPrintable = { color : "red" , print ( ) { console .log (this .color ); } }; obj.print ();
2.2 交叉类型与合并属性 当多个接口或类型具有相同的属性时,交叉类型会尝试合并它们。
原始类型属性 :如果相同属性的类型是原始类型(如 number, string),则它们必须是兼容的联合类型 。如果类型不兼容(如 string & number),则结果为 never。
非原始类型属性 :如果相同属性的类型是接口或对象字面量,则会进行深层合并 。
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 42 43 44 45 interface A { x : number ; y : string ; } interface B { y : number ; z : boolean ; } interface UserInfo { id : number ; name : string ; permissions : 'read' | 'write' ; } interface UserConfig { theme : 'dark' | 'light' ; notifications : boolean ; permissions : 'admin' | 'read' ; } type FullUser = UserInfo & UserConfig ;const fullUser : FullUser = { id : 1 , name : "Alice" , theme : "dark" , notifications : true , permissions : "read" }
三、类型别名 (Type Aliases) 类型别名允许你为任何类型定义一个新的名称。这对于复杂类型(如联合类型、交叉类型、函数类型)或为了代码可读性都非常有用。
3.1 定义与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 type EventName = 'click' | 'hover' | 'scroll' ;let event : EventName = 'click' ;type Callback = (data : string ) => void ;function registerCallback (cb : Callback ) { cb ("Data received!" ); } registerCallback ((msg ) => console .log (msg)); type Point = { x : number ; y : number ; }; let p : Point = { x : 10 , y : 20 };
3.2 接口 (Interfaces) vs 类型别名 (Type Aliases) 两者都可以定义对象类型,但存在一些关键区别:
特性
Interface (接口)
Type Alias (类型别名)
可扩展性
可以通过 extends 继承,也可以被同名接口合并 (Declaration Merging)
不能被同名合并,但可以通过交叉类型 (&) 扩展
声明形式
只能声明对象类型、函数类型、类类型
可以声明任何类型,包括原始类型、联合类型、交叉类型、元组等
应用场景
更常用于定义可扩展的对象类型和实现接口 (classes implements)
更加灵活,用于定义复杂类型、为现有类型起别名
声明合并 (Declaration Merging) 是接口的一个独有特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface User { name : string ; } interface User { age : number ; } const user : User = { name : "Alice" , age : 30 };
四、类型断言 (Type Assertions) 类型断言告诉 TypeScript 编译器:“相信我,我知道这个变量的类型是什么。”它不会进行运行时检查,仅仅是在编译时起作用。
4.1 语法
<Type>value (不推荐在 JSX 中使用,因为可能与 JSX 语法冲突)
value as Type (推荐)
4.2 使用场景 当你明确知道某个变量的类型,但 TypeScript 编译器无法识别时使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const someValue : any = "this is a string" ;let strLength1 : number = (<string >someValue).length ;console .log (strLength1); let strLength2 : number = (someValue as string ).length ;console .log (strLength2); const numValue : any = 123 ;
注意 :类型断言不是类型转换,它不会改变值的运行时类型。滥用类型断言会降低类型安全性。
五、字面量类型 (Literal Types) 字面量类型允许你指定一个变量只能是某个特定的原始字面量值(字符串、数字、布尔值)。
5.1 定义与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Direction = "up" | "down" | "left" | "right" ;let dir : Direction = "up" ;type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE" ;function handleRequest (method : HTTPMethod , url : string ) { console .log (`Handling ${method} request for ${url} ` ); } handleRequest ("GET" , "/api/users" );type Enabled = true ;let enabled : Enabled = true ;
字面量类型常与联合类型结合使用,创建更精确的集合类型。
六、模板字面量类型 (Template Literal Types) TypeScript 4.1 引入,允许你基于字符串字面量构建新的字符串字面量类型,支持字符串插值语法。
6.1 定义与使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type World = "world" ;type Greeting = `hello ${World} ` ; let x : Greeting = "hello world" ;type Color = 'red' | 'blue' ;type Size = 'small' | 'medium' | 'large' ;type ItemVariant = `${Color} -${Size} ` ;let item1 : ItemVariant = "red-medium" ;type EventName <T extends string > = `${T} Changed` | `${T} Deleted` ;type UserEvents = EventName <"user" >; type ProductEvents = EventName <"product" >;
七、索引签名 (Index Signatures) 索引签名允许你描述那些可能具有任意数量属性的对象类型,但这些属性的键和值都符合特定的类型模式。
7.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 30 31 32 33 interface StringDictionary { [key : string ]: string ; } const myDict : StringDictionary = { name : "Alice" , city : "New York" , }; console .log (myDict["name" ]); interface StringArray { [index : number ]: string ; } const names : StringArray = ["Alice" , "Bob" , "Charlie" ];console .log (names[0 ]); interface MixedDictionary { [key : string ]: string | number ; name : string ; age : number ; } const mixed : MixedDictionary = { name : "Eve" , age : 25 , hobby : "reading" };
注意 :索引签名中的键类型只能是 string、number、symbol 或模板字面量类型。如果同时存在 string 和 number 索引签名,number 索引的值类型必须是 string 索引值类型的子类型。
八、泛型 (Generics) 泛型是 TypeScript 中实现代码复用和类型安全的关键特性,它允许你在定义函数、类、接口时使用类型参数,从而使这些结构能够与多种类型一起工作,而不是局限于单一类型。
8.1 为什么需要泛型? 考虑一个返回数组最后一个元素的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function getLastAny (arr : any [] ): any { return arr[arr.length - 1 ]; } const lastNumAny = getLastAny ([1 , 2 , 3 ]); function getLastNumber (arr : number [] ): number { return arr[arr.length - 1 ]; } function getLastString (arr : string [] ): string { return arr[arr.length - 1 ]; }
使用泛型可以解决这个问题:
1 2 3 4 5 6 7 8 9 10 11 function getLast<T>(arr : T[]): T { return arr[arr.length - 1 ]; } const lastNum = getLast ([1 , 2 , 3 ]); const lastStr = getLast (["a" , "b" , "c" ]); const lastBool = getLast ([true , false ]); const specificNum = getLast<number >([1 , 2 , 3 ]);
8.2 泛型函数 定义一个函数时,在函数名后使用 <T> 来声明类型参数 T。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function merge<T, U>(obj1 : T, obj2 : U): T & U { return { ...obj1, ...obj2 }; } const merged = merge ({ name : "Alice" }, { age : 30 }); console .log (merged.name , merged.age ); interface Lengthwise { length : number ; } function loggingIdentity<T extends Lengthwise >(arg : T): T { console .log (arg.length ); return arg; } loggingIdentity ("hello" ); loggingIdentity ([1 , 2 , 3 ]);
8.3 泛型接口 接口也可以使用泛型来提高其通用性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface Pair <K, V> { key : K; value : V; } let numStringPair : Pair <number , string > = { key : 1 , value : "one" };let stringBoolPair : Pair <string , boolean > = { key : "isOpen" , value : true };function processPair<TKey , TValue >(pair : Pair <TKey , TValue >) { console .log (`Key: ${pair.key} , Value: ${pair.value} ` ); } processPair (numStringPair);
8.4 泛型类 类也可以使用泛型,通常用于构建可重用、类型安全的集合类或辅助类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class GenericBox <T> { private value : T; constructor (initialValue : T ) { this .value = initialValue; } getValue (): T { return this .value ; } setValue (newValue : T ) { this .value = newValue; } } let stringBox = new GenericBox <string >("Hello" );console .log (stringBox.getValue ()); stringBox.setValue ("World" ); let numberBox = new GenericBox (123 ); console .log (numberBox.getValue ());
九、条件类型 (Conditional Types) TypeScript 2.8 引入,允许你根据一个类型是否可以赋值给另一个类型来推断出一个新的类型,语法类似于 JavaScript 的三元运算符:T extends U ? X : Y。
9.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 type Check <T, U> = T extends U ? true : false ;type IsString = Check <string , string >; type IsNumber = Check <number , string >; type IsUnionString = Check <"hello" | "world" , string >; type IsUnionMixed = Check <"hello" | 123 , string >; type ReturnType <T> = T extends (...args : any []) => infer R ? R : any ;type FuncResult = ReturnType <() => string >; type AsyncFuncResult = ReturnType <() => Promise <number >>; type NoFuncResult = ReturnType <number >; type ElementType <T> = T extends (infer U)[] ? U : T;type StringArrayElement = ElementType <string []>; type NumberArrayElement = ElementType <number []>; type NonArrayElement = ElementType <boolean >; type UnpackPromise <T> = T extends Promise <infer U> ? U : T;type ResolvedString = UnpackPromise <Promise <string >>; type ResolvedNumber = UnpackPromise <number >;
9.2 分布式条件类型 (Distributive Conditional Types) 当 T 是一个联合类型,并且条件类型中的 extends 运算符左侧直接是裸类型参数 T 时,条件类型会分别作用于联合类型中的每个成员。
1 2 3 4 5 6 7 8 9 type ToArray <T> = T extends any ? T[] : never ;type StringOrNumberArray = ToArray <string | number >;type NotDistributive <T> = [T] extends [any ] ? T[] : never ;type NotDistributiveResult = NotDistributive <string | number >;
十、映射类型 (Mapped Types) TypeScript 2.1 引入,允许你从旧类型中创建新类型,其方式类似于使用 for ... in 遍历对象属性。它可以转换一个对象类型的每个属性。
10.1 定义与使用 语法:{ [P in K]: T },其中 K 通常是一个联合类型或 keyof AnyType。
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 interface UserProperties { id : number ; name : string ; age : number ; } type Partial <T> = { [P in keyof T]?: T[P]; }; type OptionalUser = Partial <UserProperties >;type Readonly <T> = { readonly [P in keyof T]: T[P]; }; type ReadonlyUser = Readonly <UserProperties >;type Required <T> = { [P in keyof T]-?: T[P]; }; interface OptionalProps { a ?: string ; b ?: number ; } type AllRequired = Required <OptionalProps >;type Mutable <T> = { -readonly [P in keyof T]: T[P]; }; interface ReadonlyProps { readonly x : string ; readonly y : number ; } type AllMutable = Mutable <ReadonlyProps >;
10.2 键重映射 (Key Remapping with as) TypeScript 4.1 引入,允许你在映射类型中通过 as 关键字来改变属性的键名。
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 type Getters <T> = { [P in keyof T as `get${Capitalize<string & P>} ` ]: T[P]; }; interface Person { name : string ; age : number ; } type PersonGetters = Getters <Person >;type PickByValueType <T, V> = { [P in keyof T as T[P] extends V ? P : never ]: T[P]; }; interface MyData { id : number ; name : string ; isActive : boolean ; score : number ; } type OnlyNumbers = PickByValueType <MyData , number >;
十一、内置工具类型 (Utility Types) TypeScript 内置了许多有用的工具类型,它们基于上述高级类型(尤其是映射类型和条件类型)实现,极大地简化了常见的类型转换和操作。
11.1 Partial<T> 使 T 的所有属性可选。
1 2 3 4 5 6 7 8 9 10 11 12 13 interface Todo { title : string ; description : string ; completed : boolean ; } function updateTodo (todo : Todo , fieldsToUpdate : Partial <Todo > ) { return { ...todo, ...fieldsToUpdate }; } const todo1 = { title : "organize desk" , description : "clear clutter" , completed : false };const updatedTodo = updateTodo (todo1, { description : "throw out trash" });
11.2 Readonly<T> 使 T 的所有属性只读。
1 2 3 4 5 6 7 interface Point { x : number ; y : number ; } const p1 : Readonly <Point > = { x : 10 , y : 20 };
11.3 Record<K, T> 构造一个对象类型,其属性键为 K (可以是字面量联合类型或 string/number 等),属性值为 T。
1 2 3 4 5 6 7 8 9 10 11 12 type Page = "home" | "about" | "contact" ;interface PageInfo { title : string ; path : string ; } const pages : Record <Page , PageInfo > = { home : { title : "Home Page" , path : "/" }, about : { title : "About Us" , path : "/about" }, contact : { title : "Contact Us" , path : "/contact" }, };
11.4 Pick<T, K> 从类型 T 中选择一组属性 K(K 必须是 T 的属性名的联合类型),构造一个新的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface UserProfile { id : number ; name : string ; email : string ; avatarUrl : string ; } type UserSummary = Pick <UserProfile , "id" | "name" >;const summary : UserSummary = { id : 1 , name : "Alice" };
11.5 Omit<T, K> 从类型 T 中排除一组属性 K,构造一个新的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface UserProfile { id : number ; name : string ; email : string ; avatarUrl : string ; } type UserDetailsWithoutAvatar = Omit <UserProfile , "avatarUrl" >;const userDetails : UserDetailsWithoutAvatar = { id : 1 , name : "Bob" , email : "bob@example.com" };
11.6 Exclude<T, U> 从类型 T 中排除可以赋值给 U 的类型成员(通常用于联合类型)。
1 2 3 4 type AllColors = "red" | "green" | "blue" | "white" | "black" ;type BasicColors = "red" | "green" | "blue" ;type RemainingColors = Exclude <AllColors , BasicColors >;
从类型 T 中提取可以赋值给 U 的类型成员(通常用于联合类型)。
1 2 3 4 type AllColors = "red" | "green" | "blue" | "white" | "black" ;type BasicColors = "red" | "green" | "pink" ; type CommonColors = Extract <AllColors , BasicColors >;
11.8 NonNullable<T> 从类型 T 中排除 null 和 undefined。
1 2 type PossibleNull = string | number | null | undefined ;type NotNull = NonNullable <PossibleNull >;
11.9 Parameters<T> 提取函数类型 T 的参数类型组成的元组类型。
1 2 3 4 5 6 function greet (name : string , age : number ): string { return `Hello ${name} , you are ${age} years old.` ; } type GreetParams = Parameters <typeof greet>; const params : GreetParams = ["Alice" , 30 ];
11.10 ReturnType<T> 提取函数类型 T 的返回类型。
1 2 3 4 5 function calculateSum (a : number , b : number ): number { return a + b; } type SumResult = ReturnType <typeof calculateSum>;
11.11 Awaited<T> (TypeScript 4.5+) 递归地解包 Promise 类型,提取其最终的解析值类型。
1 2 3 4 5 6 7 8 9 10 type P1 = Promise <string >;type P2 = Promise <Promise <number >>;type P3 = Promise <string | Promise <boolean >>;type P4 = Promise <string | Promise <boolean > | Promise <number >[]>; type AwaitedP1 = Awaited <P1 >; type AwaitedP2 = Awaited <P2 >; type AwaitedP3 = Awaited <P3 >; type AwaitedP4 = Awaited <P4 >; type StringOrNumber = Awaited <string | Promise <number >>;
十二、总结 TypeScript 的高级类型是其强大类型系统的基石,它们使得我们能够:
增强类型安全性 :通过精确的类型定义,减少运行时错误。
提高代码可读性和可维护性 :复杂逻辑可以用清晰的类型结构表示。
实现强大的代码补全和重构 :IDE 可以根据类型信息提供更智能的帮助。
构建可复用和灵活的代码 :泛型和工具类型允许创建适用于多种场景的通用组件。
从基础的联合类型和交叉类型,到进阶的条件类型、映射类型以及内置工具类型,每一种高级类型都有其独特的应用场景。熟练掌握它们将显著提升你的 TypeScript 开发效率和代码质量。