TypeScript 高级类型入门手册:附大量代码实例

时间:2022-11-20 23:29:09

TypeScript 高级类型入门手册:附大量代码实例

TypeScript 是一种类型化的语言,允许你指定变量的类型,函数参数,返回的值和对象属性。

你可以把本文看做一个带有示例的 TypeScript 高级类型备忘单

让我们开始吧!

Intersection Types(交叉类型)

交叉类型是一种将多种类型组合为一种类型的方法。这意味着你可以将给定的类型 A 与类型 B 或更多类型合并,并获得具有所有属性的单个类型。

  1. type LeftType = {  
  2.     id: number;  
  3.     left: string;  
  4. };  
  5. type RightType = {  
  6.     id: number;  
  7.     right: string;  
  8. };  
  9. type IntersectionType = LeftType & RightType;  
  10. function showType(args: IntersectionType) {  
  11.     console.log(args);  
  12.  
  13. showType({ id: 1, left: 'test', right: 'test' });  
  14. // Output: {id: 1, left: "test", right: "test"} 

如你所见,IntersectionType组合了两种类型-LeftType和RightType,并使用&符号形成了交叉类型。

Union Types(联合类型)

联合类型使你可以赋予同一个变量不同的类型

  1. type UnionType = string | number;  
  2. function showType(arg: UnionType) {  
  3.     console.log(arg); 
  4.  
  5. showType('test');  
  6. // Output: test  
  7. showType(7);  
  8. // Output: 7 

函数showType是一个联合类型函数,它接受字符串或者数字作为参数。

Generic Types(泛型)

泛型类型是复用给定类型的一部分的一种方式。它有助于捕获作为参数传递的类型 T。

优点: 创建可重用的函数,一个函数可以支持多种类型的数据。这样开发者就可以根据自己的数据类型来使用函数

泛型函数 

  1. function showType<T>(args: T) {  
  2.     console.log(args);  
  3.  
  4. showType('test'); 
  5. // Output: "test"  
  6. showType(1);  
  7. // Output: 1 

如何创建泛型类型:需要使用<>并将 T(名称可自定义)作为参数传递。上面的栗子中, 我们给 showType 添加了类型变量 T。T帮助我们捕获用户传入的参数的类型(比如:number/string)之后我们就可以使用这个类型

我们把 showType 函数叫做泛型函数,因为它可以适用于多个类型

泛型接口 

  1. interface GenericType<T> {  
  2.     id: number;  
  3.     name: T;  
  4.  
  5. function showType(args: GenericType<string>) {  
  6.     console.log(args);  
  7.  
  8. showType({ id: 1, name: 'test' });  
  9. // Output: {id: 1, name: "test"}  
  10. function showTypeTwo(args: GenericType<number>) {  
  11.     console.log(args);  
  12.  
  13. showTypeTwo({ id: 1, name: 4 });  
  14. // Output: {id: 1, name: 4} 

在上面的栗子中,声明了一个 GenericType 接口,该接口接收泛型类型 T, 并通过类型 T来约束接口内 name 的类型

注:泛型变量约束了整个接口后,在实现的时候,必须指定一个类型

因此在使用时我们可以将name设置为任意类型的值,示例中为字符串或数字

多参数的泛型类型 

  1. interface GenericType<T, U> {  
  2.     id: T;  
  3.     name: U;  
  4.  
  5. function showType(args: GenericType<number, string>) { 
  6.     console.log(args); 
  7.  
  8. showType({ id: 1, name: 'test' });  
  9. // Output: {id: 1, name: "test"}   
  10. function showTypeTwo(args: GenericType<string, string[]>) {  
  11.     console.log(args);  
  12.  
  13. showTypeTwo({ id: '001', name: ['This', 'is', 'a', 'Test'] });  
  14. // Output: {id: "001", name: Array["This", "is", "a", "Test"]} 

泛型类型可以接收多个参数。在上面的代码中,我们传入两个参数:T和U,然后将它们用作id,name的类型。也就是说,我们现在可以使用该接口并提供不同的类型作为参数。

Utility Types

TypeScript 内部也提供了很多方便实用的工具,可帮助我们更轻松地操作类型。如果要使用它们,你需要将类型传递给<>

Partial

  •  Partial<T>

Partial 允许你将T类型的所有属性设为可选。它将在每一个字段后面添加一个?。

  1. interface PartialType {  
  2.     id: number;  
  3.     firstName: string;  
  4.     lastName: string;  
  5.  
  6. /*  
  7. 等效于  
  8. interface PartialType {  
  9.   id?: number  
  10.   firstName?: string  
  11.   lastName?: string  
  12.  
  13. */ 
  14. function showType(args: Partial<PartialType>) {  
  15.     console.log(args);  
  16.  
  17. showType({ id: 1 });  
  18. // Output: {id: 1}  
  19. showType({ firstName: 'John', lastName: 'Doe' });  
  20. // Output: {firstName: "John", lastName: "Doe"} 

上面代码中声明了一个PartialType接口,它用作函数showType()的参数的类型。为了使所有字段都变为可选,我们使用Partial关键字并将PartialType类型作为参数传递。

Required

  •  Required<T>

将某个类型里的属性全部变为必选项

  1. interface RequiredType { 
  2.     id: number;  
  3.     firstName?: string;  
  4.     lastName?: string;  
  5.  
  6. function showType(args: Required<RequiredType>) {  
  7.     console.log(args);  
  8. showType({ id: 1, firstName: 'John', lastName: 'Doe' });  
  9. // Output: { id: 1, firstName: "John", lastName: "Doe" }  
  10. showType({ id: 1 });  
  11. // Error: Type '{ id: number: }' is missing the following properties from type 'Required<RequiredType>': firstName, lastName 

上面的代码中,即使我们在使用接口之前先将某些属性设为可选,但Required被加入后也会使所有属性成为必选。如果省略某些必选参数,TypeScript 将报错。

Readonly

  •  Readonly<T>

会转换类型的所有属性,以使它们无法被修改

  1. interface ReadonlyType {  
  2.     id: number;  
  3.     name: string;  
  4.  
  5. function showType(args: Readonly<ReadonlyType>) {  
  6.     args.id = 4 
  7.     console.log(args);  
  8.  
  9. showType({ id: 1, name: 'Doe' });  
  10. // Error: Cannot assign to 'id' because it is a read-only property. 

我们使用Readonly来使ReadonlyType的属性不可被修改。也就是说,如果你尝试为这些字段之一赋予新值,则会引发错误。

除此之外,你还可以在指定的属性前面使用关键字readonly使其无法被重新赋值

  1. interface ReadonlyType {  
  2.     readonly id: number;  
  3.     name: string;  

Pick

  •  Pick<T, K>

此方法允许你从一个已存在的类型 T中选择一些属性作为K, 从而创建一个新类型

即 抽取一个类型/接口中的一些子集作为一个新的类型

T代表要抽取的对象 K有一个约束: 一定是来自T所有属性字面量的联合类型 新的类型/属性一定要从K中选取,

  1. /** 
  2.     源码实现  
  3.  * From T, pick a set of properties whose keys are in the union K  
  4.  */  
  5. type Pick<T, K extends keyof T> = {  
  6.     [P in K]: T[P];  
  7. }; 
  1. interface PickType {  
  2.     id: number;  
  3.     firstName: string;  
  4.     lastName: string;  
  5.  
  6. function showType(args: Pick<PickType, 'firstName' | 'lastName'>) {  
  7.     console.log(args);  
  8.  
  9. showType({ firstName: 'John', lastName: 'Doe' });  
  10. // Output: {firstName: "John"}  
  11. showType({ id: 3 });  
  12. // Error: Object literal may only specify known properties, and 'id' does not exist in type 'Pick<PickType, "firstName" | "lastName">

Pick 与我们前面讨论的工具有一些不同,它需要两个参数

  •  T是要从中选择元素的类型
  •  K是要选择的属性(可以使使用联合类型来选择多个字段)

Omit

  • Omit<T, K>

Omit的作用与Pick类型正好相反。不是选择元素,而是从类型T中删除K个属性。

  1. interface PickType { 
  2.     id: number;  
  3.     firstName: string;  
  4.     lastName: string;  
  5.  
  6. function showType(args: Omit<PickType, 'firstName' | 'lastName'>) {  
  7.     console.log(args);  
  8.  
  9. showType({ id: 7 });  
  10. // Output: {id: 7}   
  11. showType({ firstName: 'John' });  
  12. // Error: Object literal may only specify known properties, and 'firstName' does not exist in type 'Pick<PickType, "id">

Extract

  •  Extract<T, U>

提取T中可以赋值给U的类型--取交集

Extract允许你通过选择两种不同类型中的共有属性来构造新的类型。也就是从T中提取所有可分配给U的属性。

  1. interface FirstType { 
  2.     id: number;  
  3.     firstName: string;  
  4.     lastName: string;  
  5.  
  6. interface SecondType {  
  7.     id: number;  
  8.     address: string;  
  9.     city: string;  
  10.   
  11. type ExtractExtractType = Extract<keyof FirstType, keyof SecondType> 
  12. // Output: "id" 

在上面的代码中,FirstType接口和SecondType接口,都存在 id:number属性。因此,通过使用Extract,即提取出了新的类型 {id:number}。

Exclude

  •  Exclude<T, U> --从 T 中剔除可以赋值给 U 的类型。

与Extract不同,Exclude通过排除两个不同类型中已经存在的共有属性来构造新的类型。它会从T中排除所有可分配给U的字段。

  1. interface FirstType {  
  2.     id: number;  
  3.     firstName: string;  
  4.     lastName: string;  
  5.  
  6. interface SecondType {  
  7.     id: number;  
  8.     address: string;  
  9.     city: string;  
  10.  
  11. type ExcludeExcludeType = Exclude<keyof FirstType, keyof SecondType> 
  12. // Output; "firstName" | "lastName" 

上面的代码可以看到,属性firstName和lastName 在SecondType类型中不存在。通过使用Extract关键字,我们可以获得T中存在而U中不存在的字段。

Record

  •  Record<K,T>

此工具可帮助你构造具有给定类型T的一组属性K的类型。将一个类型的属性映射到另一个类型的属性时,Record非常方便。

  1. interface EmployeeType {  
  2.     id: number;  
  3.     fullname: string;  
  4.     role: string;  
  5.  
  6. let employees: Record<number, EmployeeType> = {  
  7.     0: { id: 1, fullname: 'John Doe', role: 'Designer' },  
  8.     1: { id: 2, fullname: 'Ibrahima Fall', role: 'Developer' },  
  9.     2: { id: 3, fullname: 'Sara Duckson', role: 'Developer' },  
  10. }; 
  11. // 0: { id: 1, fullname: "John Doe", role: "Designer" },  
  12. // 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },  
  13. // 2: { id: 3, fullname: "Sara Duckson", role: "Developer" } 

Record的工作方式相对简单。在代码中,它期望一个number作为类型,这就是为什么我们将 0、1 和 2 作为employees变量的键的原因。如果你尝试使用字符串作为属性,则会引发错误,因为属性是由EmployeeType给出的具有 ID,fullName 和 role 字段的对象。

NonNullable

  •  NonNullable<T>

  -- 从 T 中剔除 null 和 undefined

  1. type NonNullableType = string | number | null | undefined;  
  2. function showType(args: NonNullable<NonNullableType>) {  
  3.     console.log(args);  
  4.  
  5. showType('test');  
  6. // Output: "test"  
  7. showType(1);  
  8. // Output: 1  
  9. showType(null);  
  10. // Error: Argument of type 'null' is not assignable to parameter of type 'string | number'.  
  11. showType(undefined);  
  12. // Error: Argument of type 'undefined' is not assignable to parameter of type 'string | number'. 

我们将类型NonNullableType作为参数传递给NonNullable,NonNullable通过排除null和undefined来构造新类型。也就是说,如果你传递可为空的值,TypeScript 将引发错误。

顺便说一句,如果将--strictNullChecks标志添加到tsconfig文件,TypeScript 将应用非空性规则。

Mapped Types( 映射类型)

映射类型允许你从一个旧的类型,生成一个新的类型。

请注意,前面介绍的某些高级类型也是映射类型。如:

  1. /*  
  2. Readonly, Partial和 Pick是同态的,但 Record不是。 因为 Record并不需要输入类型来拷贝属性,所以它不属于同态:  
  3. */  
  4. type Readonly<T> = {  
  5.     readonly [P in keyof T]: T[P];  
  6. };  
  7. type Partial<T> = {  
  8.     [P in keyof T]?: T[P];  
  9. };  
  10. type Pick<T, K extends keyof T> = {  
  11.     [P in K]: T[P];  
  12. }; 

Record; 

  1. type StringMap<T> = {  
  2.     [P in keyof T]: string;  
  3. };  
  4. function showType(arg: StringMap<{ id: number; name: string }>) { 
  5.     console.log(arg); 
  6.  
  7. showType({ id: 1, name: 'Test' });  
  8. // Error: Type 'number' is not assignable to type 'string'.  
  9. showType({ id: 'testId', name: 'This is a Test' });  
  10. // Output: {id: "testId", name: "This is a Test"} 

StringMap<>会将传入的任何类型转换为字符串。就是说,如果我们在函数showType()中使用它,则接收到的参数必须是字符串-否则,TypeScript 将引发错误。

Type Guards(类型保护)

类型保护使你可以使用运算符检查变量或对象的类型。这是一个条件块,它使用typeof,instanceof或in返回类型。

typescript 能够在特定区块中保证变量属于某种确定类型。可以在此区块中放心地引用此类型的属性,或者调用此类型的方法

typeof 

  1. function showType(x: number | string) {  
  2.     if (typeof x === 'number') {  
  3.         return `The result is ${x + x}`;  
  4.     }  
  5.     throw new Error(`This operation can't be done on a ${typeof x}`);  
  6.  
  7. showType("I'm not a number");  
  8. // Error: This operation can't be done on a string  
  9. showType(7);  
  10. // Output: The result is 14 

什么代码中,有一个普通的 JavaScript 条件块,通过typeof检查接收到的参数的类型。

instanceof 

  1. class Foo {  
  2.     bar() {  
  3.         return 'Hello World';  
  4.     }  
  5.  
  6. class Bar {  
  7.     baz = '123' 
  8.  
  9. function showType(arg: Foo | Bar) {  
  10.     if (arg instanceof Foo) {  
  11.         console.log(arg.bar());  
  12.         return arg.bar();  
  13.     }  
  14.     throw new Error('The type is not supported');  
  15.  
  16. showType(new Foo());  
  17. // Output: Hello World   
  18. showType(new Bar());  
  19. // Error: The type is not supported 

像前面的示例一样,这也是一个类型保护,它检查接收到的参数是否是Foo类的一部分,并对其进行处理。

in 

  1. interface FirstType {  
  2.     x: number;  
  3.  
  4. interface SecondType {  
  5.     y: string;  
  6.  
  7. function showType(arg: FirstType | SecondType) {  
  8.     if ('x' in arg) {  
  9.         console.log(`The property ${arg.x} exists`);  
  10.         return `The property ${arg.x} exists`;  
  11.     }  
  12.     throw new Error('This type is not expected');  
  13.  
  14. showType({ x: 7 });  
  15. // Output: The property 7 exists  
  16. showType({ y: 'ccc' });  
  17. // Error: This type is not expected 

什么的栗子中,使用in检查参数对象上是否存在属性x。

Conditional Types(条件类型)

条件类型测试两种类型,然后根据该测试的结果选择其中一种。

一种由条件表达式所决定的类型, 表现形式为 T extends U ? X : Y , 即如果类型T可以被赋值给类型U,那么结果类型就是X类型,否则为Y类型。

条件类型使类型具有了不唯一性,增加了语言的灵活性,

  1. // 源码实现  
  2. type NonNullable<T> = T extends null | undefined ? never : T;  
  3. // NotNull<T> 等价于 NoneNullable<T,U>  
  4. // 用法示例  
  5. type resType = NonNullable<string | number | null | undefined>; // string|number 

上面的代码中, NonNullable检查类型是否为 null,并根据该类型进行处理。正如你所看到的,它使用了 JavaScript 三元运算符。

原文地址:https://mp.weixin.qq.com/s/TcZ0DgtvVEF_Hp88X5E7oA