Day4 TypeScript进阶用法,让代码更加通用 | 青训营

86 阅读9分钟

高阶用法

泛型(Generics)

泛型允许您编写可重用的代码,以处理多种类型的数据。它们是在编译时确定的,可以帮助您在编译时捕获类型错误。

  1. 泛型函数
function identity<T>(arg: T): T {
  return arg;
}

// 使用泛型函数
let result = identity<string>('Hello');
console.log(result); // Output: Hello

let result2 = identity<number>(42);
console.log(result2); // Output: 42
  1. 泛型类
class Queue<T> {
  private elements: T[] = [];

  enqueue(element: T): void {
    this.elements.push(element);
  }

  dequeue(): T | undefined {
    return this.elements.shift();
  }
}

// 使用泛型类
const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
numberQueue.enqueue(3);

console.log(numberQueue.dequeue()); // Output: 1

const stringQueue = new Queue<string>();
stringQueue.enqueue('Hello');
stringQueue.enqueue('World');

console.log(stringQueue.dequeue()); // Output: Hello
  1. 泛型接口
//可定义接口中可修改的类型
interface Pair<T, U> {
  first: T;
  second: U;
}

// 使用泛型接口
const pair: Pair<string, number> = {
  first: 'one',
  second: 1,
};
console.log(pair); // Output: { first: 'one', second: 1 }
  1. 泛型的常见陷阱

使用泛型类型参数作为具体类型:在泛型函数或类内部,类型参数被视为变量,不能直接用作具体类型。例如,以下代码是错误的:

```ts
function identity<T>(arg: T): T {
  let variable: T; // Error: Type parameter 'T' cannot be used as an individual type
  // ...
}
```

如果需要在函数或类内部使用泛型类型参数作为具体类型,可以使用类型注解或类型别名来实现,如下:
可以使用类型别名来为泛型类型参数创建一个具名类型,然后在函数或类内部使用该类型别名作为具体类型。这样,您就可以在内部使用泛型类型参数作为具体类型。例如:

type GenericType<T> = T;

function identity<T>(arg: T): T {
  let variable: GenericType<T>;
  // ...
  return variable;
}

接口继承(Interface Inheritance)

接口继承允许您从一个或多个接口继承属性和方法,并将它们组合成一个新的接口。这可以使您的代码更加模块化和可重用。 在TypeScript中,接口继承指的是一个接口可以从一个或多个接口继承属性和方法,并将它们组合成一个新的接口。这使得您可以将多个接口的属性和方法组合在一起,以创建一个更具体的接口。

接口继承使用extends关键字来指定要继承的接口。例如,假设您有以下两个接口:

interface Person {
  name: string;
  age: number;
}

interface Employee {
  id: number;
  salary: number;
}

可以使用接口继承来创建一个新的接口,该接口继承自这两个接口:

interface EmployeeInfo extends Person, Employee {
  department: string;
}

在上面的示例中,EmployeeInfo接口继承了PersonEmployee接口,并添加了一个新的department属性。现在,EmployeeInfo接口包含了所有三个接口的属性,这使得您可以更方便地定义一个包含所有员工信息的接口。

类型别名(Type Aliases)

类型别名允许您为任何类型创建一个别名,这可以使您的代码更加清晰、易于阅读和维护。 在TypeScript中,类型别名允许为任何类型创建一个别名。类型别名使用type关键字来定义,例如:

type Age = number;
type Person = {
  name: string;
  age: Age;
};

在上面的示例中,我们使用type关键字来定义了两个类型别名:AgePersonAge别名将number类型定义为年龄,而Person别名是一个具有nameage属性的对象。现在,我们可以使用这些别名来定义变量和函数参数,例如:

function printPersonInfo(person: Person) {
  console.log(`Name: ${person.name}, Age: ${person.age}`);
}

const john: Person = {
  name: 'John',
  age: 30
};

printPersonInfo(john);

在上面的示例中,我们使用Person别名定义了一个函数参数和一个对象变量。这使得代码更加清晰和易于阅读,因为我们可以使用Person别名来描述一个具有特定属性的对象类型。

类型别名还允许您定义复杂的类型,例如联合类型和交叉类型。例如,以下是一个使用类型别名定义联合类型的示例:

type Result = { success: true, value: number } | { success: false, error: string };

在上面的示例中,我们使用type关键字定义了一个Result别名,它是一个联合类型,可以是成功的对象(包含successvalue属性)或失败的对象(包含successerror属性)。

总之,类型别名是TypeScript中非常有用的一种功能,它允许您为任何类型创建一个别名,使得代码更加清晰、易于阅读和维护。它们还可以用于定义复杂的类型,例如联合类型和交叉类型。

类型推断(Type Inference)

TypeScript的类型推断功能可以自动推断变量的类型,这可以使您的代码更加简洁和易于编写。

枚举(Enums)

枚举是一种特殊的类型,它允许您为一组相关的常量赋予一个名称。这可以使您的代码更加可读和易于理解。

高级类型(Advanced Types)

TypeScript提供了各种高级类型,例如交叉类型、联合类型、条件类型等,可以让您更加灵活地处理复杂的数据结构和逻辑。 TypeScript中的高级类型包括联合类型、交叉类型、类型别名、泛型、映射类型、条件类型、索引类型、可选属性和部分属性等。下面对每个高级类型进行简要介绍:

联合类型(Union Types)

联合类型表示一个变量可以是多种类型中的一种。例如,string | number表示一个变量可以是字符串或数字类型。
常用属性:|(联合类型操作符)、typeof(获取变量的类型)、extends(约束泛型类型的类型参数)

交叉类型(Intersection Types)

交叉类型表示一个变量包含多种类型的属性。例如,A & B表示一个变量包含类型A和类型B的属性。

常用属性:&(交叉类型操作符)

类型别名(Type Aliases)

类型别名允许您为任何类型创建一个别名。它们可以使您的代码更加清晰、易于阅读和维护。

常用属性:type(定义类型别名关键字)

泛型(Generics)

泛型允许您在定义函数、类和接口时使用类型参数。这使得您可以编写更通用和可重用的代码。

常用属性:<T>(定义类型参数)、extends(约束泛型类型的类型参数)

映射类型(Mapped Types)

映射类型允许您从现有类型中创建新类型。例如,您可以使用映射类型将所有属性变为可选属性。

常用属性:{ [P in K]: T }(将现有类型的属性映射为新的属性)、Partial<T>(将所有属性变为可选属性)

映射类型是TypeScript中的一种高级类型,它允许您从现有类型中创建新类型。映射类型可以将现有类型的属性进行转换、过滤或批量修改,从而生成一个新的类型。在TypeScript中,映射类型是通过使用索引类型和条件类型来实现的。

映射类型可以用以下语法来定义,下列是一些常见的用法:

type NewType = { [P in keyof OldType]: NewPropertyType };

在上面的语法中,OldType是要进行转换的现有类型,NewPropertyType是新的属性类型,NewType是生成的新类型。

例如,考虑以下接口:

interface Person {
  name: string;
  age: number;
  address: string;
}
  1. 可选,现在,如果想将Person接口中的所有属性都变为可选属性,可以使用映射类型来实现:
type PartialPerson = { [P in keyof Person]?: Person[P] };

在上面的示例中,PartialPerson是一个新的类型,它是通过将Person接口中的所有属性转换为可选属性来生成的。在映射类型中,[P in keyof Person]表示将Person接口中的所有属性进行映射,?表示将它们变为可选属性。

除了上述提到的将属性变为可选属性之外,还可以进行其他类型转换和操作。下面是一些映射类型的其他常见用法:

  1. 只读,将属性变为只读属性:

    type ReadonlyPerson = { readonly [P in keyof Person]: Person[P] };
    

    在上面的示例中,使用readonly关键字将Person接口中的所有属性转换为只读属性。

  2. 移除,从现有类型中移除某些属性:

    type OmitPerson = Omit<Person, 'address'>;
    

    上面的示例中,使用Omit类型工具从Person接口中移除了名为'address'的属性,生成了一个新的类型。

  3. 新增,添加新的属性:

    type ExtendedPerson = Person & { gender: string };
    

    在上面的示例中,通过使用交叉类型操作符&,将Person接口与一个包含新属性'gender'的对象类型进行合并,生成了一个包含新属性的扩展类型。

  4. 修改,根据现有属性生成新的属性:

    type PersonWithUpperCaseName = { [P in keyof Person as `upper_${string & P}`]: string };
    

    上面的示例中,使用as关键字和模板字符串,将Person接口中的所有属性进行映射,并生成一个带有前缀'upper_'的新属性。

映射类型还可以进行其他类型转换,例如将所有属性变为只读属性、移除某些属性、添加新的属性等。通过使用映射类型,您可以轻松地批量操作类型并生成新的类型,这使得代码更加通用和可维护。

条件类型(Conditional Types)

条件类型允许您在类型上进行条件分支。例如,您可以使用条件类型来确定一个变量是否为可选属性。

常用属性:T extends U ? X : Y(根据类型T是否满足类型U来确定返回类型)

索引类型(Index Types)

索引类型允许您使用字符串或数字类型的索引来访问对象的属性。例如,keyof T表示对象T的属性名称的联合类型。

常用属性:keyof T(获取对象T的属性名称的联合类型)、T[K](获取对象T中属性K的类型) 一个常见的应用是使用索引类型来实现类型安全的映射或转换操作。让我们通过一个示例来说明。

假设我们有一个名为User的接口,它包含用户的一些基本信息:

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

现在,我们想要创建一个新的类型UserDTO,它是User类型的一个子集,只包含部分属性,并且属性名以dto_作为前缀。我们可以使用索引类型和映射类型来实现这个转换:

type UserDTO = {
  [P in keyof User as `dto_${string & P}`]: User[P];
};

在上面的例子中,我们使用了映射类型的语法:[P in keyof User as dto_${string & P}]。这表示我们遍历User接口中的每个属性P,并生成一个新的属性名,前缀为dto_,然后将其与相应的属性值User[P]进行映射。

现在,我们可以创建一个User对象,并将其转换为UserDTO类型

const user: User = {
  id: 1,
  name: 'John Doe',
  email: 'john@example.com',
  age: 30,
};

const userDTO: UserDTO = {
  dto_id: user.id,
  dto_name: user.name,
  dto_email: user.email,
  dto_age: user.age,
};

可选属性(Optional Properties)

可选属性允许您在定义接口时指定某些属性为可选属性。这使得您可以定义一些属性是可选的,而不是必需的。
常用属性:?(指定属性为可选属性操作符)

部分属性(Partial Properties)

部分属性允许您在定义接口时将所有属性变为可选属性。这使得您可以定义一个对象,该对象可能只包含一部分属性。

常用属性:Partial<T>(将所有属性变为可选属性),意思就是我使用Partial然后在对象里输入我需要的person里的属性就可以生成只有这些属性的接口了。

eg:

interface Person {
  name: string;
  age: number;
  address: string;
}

const partialPerson: Partial<Person> = {
  name: 'John',
  age: 30,
};

装饰器(Decorators)

装饰器是一种特殊的语法,它允许您在类、方法、属性等上面添加元数据和行为。这可以使您的代码更加模块化和可扩展。(个人感觉有点像对数据的备注信息...