跟着AI助理学习TypeScript,这一篇文章足够了

259 阅读16分钟

我: 我想学习Typescript的技术知识,从基础到进阶的所有知识点。你要作为指导老师陪我一起学习,仔细讲解知识点,先列一个学习清单出来

AI: 当然可以,以下是我们可以一起学习的 TypeScript 的知识清单:

  1. 基础部分:

    • 变量声明
    • 数据类型
    • 数组与元组
    • 函数(包括可选参数、默认参数、剩余参数等)
    • 类型推论
    • 接口(包括可选属性、只读属性、额外的属性检查等)
  2. 中级部分:

    • 类(包括继承、静态属性、存取器、抽象类等)
    • 函数(包括重载、泛型函数等)
    • 泛型(包括泛型类型、泛型类、泛型约束等)
    • 高级类型(包括交叉类型、联合类型、类型别名、字符串字面量类型、数值字面量类型、可辨识联合、映射类型等)
    • 枚举
    • 类型断言与类型保护
  3. 进阶部分:

    • 模块
    • 声明合并
    • 装饰器
    • Mixins
    • 异步处理(包括 Promise、async/await 等)
    • 工具类型(包括 Partial、Required、Readonly、Record、Pick、Omit、Exclude、Extract、NonNullable 等)

基础部分

变量声明

在 TypeScript 中,我们使用 let 和 const 来声明变量。与 JavaScript 类似,let 允许你声明一个可以重新赋值的变量,而 const 允许你声明一个常量,即一旦赋值就不能更改。

示例:

let myName: string = "Alice";
const pi: number = 3.14;

在上面的示例中,我们还看到了类型注解。TypeScript 允许我们为变量指定类型。在这个示例中,myName 的类型被指定为 string,而 pi 的类型被指定为 number。

如果你尝试给 myName 赋一个非字符串的值,TypeScript 会给你一个错误。

数据类型

在 TypeScript 中,我们主要有以下数据类型:

1. Boolean

这是最基本的数据类型,代表逻辑值:true 或 false。

typescriptCopy code
let isDone: boolean = false;

2. Number

与 JavaScript 一样,TypeScript 中的所有数字都是浮点值。这些浮点数的类型是 number。

typescriptCopy code
let decimal: number = 6;

3. String

用于表示文本数据类型。你可以使用双引号或者单引号来表示字符串。

typescriptCopy code
let color: string = "blue";

4. Array

TypeScript 中数组类型有两种表示方式。一种是在元素类型后面直接跟上 []:

typescriptCopy code
let list: number[] = [1, 2, 3];

另一种方式是使用数组泛型:

5. Tuple

元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同。

let x: [string, number];
x = ["hello", 10]; // OK

6. Enum

enum 类型是对 JavaScript 标准数据类型的一个补充。

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];
console.log(colorName);  // 显示'Green',因为上面代码里,Green的值就是2

7. Any

有时,我们需要在编程阶段还不清楚类型的变量指定一个类型。这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any 类型来标记这些变量:

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

8. Void

void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:

function warnUser(): void {
    console.log("This is my warning message");
}

9. Null and Undefined

undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null。

let u: undefined = undefined;
let n: null = null;

这些就是 TypeScript 中的基本数据类型。掌握了这些,你就可以更好地理解 TypeScript 如何帮助我们在编程过程中进行类型检查,以及如何使用类型来编写更清晰、更可维护的代码。

数组与元祖

1. 数组

在 TypeScript 中,我们可以像在 JavaScript 中那样使用数组。数组的每个元素都应该是同一类型。我们可以使用两种方式定义数组:

  1. 直接在元素类型后面添加 []。例如,我们可以定义一个只包含数字的数组:
let numbers: number[] = [1, 2, 3, 4, 5];
  1. 使用数组泛型 Array。比如,我们可以定义一个只包含字符串的数组:
let fruits: Array<string> = ['apple', 'banana', 'mango'];

我们可以通过索引访问和修改数组中的元素:

numbers[0] = 10;
console.log(numbers[0]); // Output: 10

如果你想在一个数组中同时存储字符串和数字,你可以使用联合类型。联合类型允许你将任意两种或更多种类型组合在一起。例如:

let array: (number | string)[] = [1, "apple", 2, "banana"];

在上面的代码中,我们定义了一个数组,它的元素可以是数字(number)或者字符串(string)。然后我们在数组中混合了数字和字符串元素。

还有另一种方式是使用数组泛型:

let array: Array<number | string> = [1, "apple", 2, "banana"];

这两种方式都允许你在同一个数组中混合存储数字和字符串。你可以根据自己的喜好选择其中一种方式。

2. 元组

元组是 TypeScript 中的一个特殊类型,它允许你创建一个已知元素数量和类型的数组,元素类型不必相同。例如,你可以定义一个元组,它包含一个字符串和一个数字:

let tuple: [string, number];
tuple = ["hello", 10]; // OK

在元组中,数据类型的顺序必须与定义元组时的类型顺序一致。否则,你将会得到一个错误:

tuple = [10, "hello"]; // Error

你也可以访问元组中的元素:

console.log(tuple[0]); // Output: "hello"

需要注意的是,当访问一个越界的元素,会使用联合类型替代:

tuple[3] = "world"; // OK, 字符串可以赋值给(string | number)类型
console.log(tuple[5].toString()); // OK, 'string' 和 'number' 都有 toString
tuple[6] = true; // Error, 布尔不是(string | number)类型

函数

函数是 TypeScript 和 JavaScript 中的重要特性之一。函数用于封装具有特定功能的代码片段,使其可以在代码中的多个位置复用。

1. 函数有返回值

在 TypeScript 中,函数类型包括两部分:函数的参数类型和函数的返回值类型。让我们来看一个示例:

function greet(name: string): string {
  return `Hello, ${name}`;
}

let greeting = greet('Alice');

在上面的例子中,我们定义了一个函数 greet,这个函数接收一个名为 name 的参数,这个参数是字符串类型,然后返回一个字符串类型的值。

2. 函数没有返回值

如果函数没有返回值(也就是说,如果函数在执行完毕后没有产生结果),那么你可以使用 void 类型来指定返回类型。例如:

function warnUser(warning: string): void {
  console.log(`Warning: ${warning}`);
}

在上面的例子中,warnUser 函数的返回值类型为 void,表示该函数没有返回值。

3. 函数可选参数和默认参数

函数还可以包含可选参数和默认参数。可选参数在参数名后面添加一个 ? 符号,表示该参数在调用函数时可以省略。默认参数在参数声明中包含一个默认值。例如:

function greet(name: string, greeting: string = 'Hello'): string {
  return `${greeting}, ${name}`;
}

console.log(greet('Alice'));  // Outputs: 'Hello, Alice'
console.log(greet('Alice', 'Hi'));  // Outputs: 'Hi, Alice'

在这个例子中,greeting 参数有一个默认值 'Hello'。当你在调用 greet 函数时不提供 greeting 参数的值,函数会使用默认值。、

4. 函数剩余参数

与 JavaScript 一样,TypeScript 函数也支持剩余参数,允许你将任意数量的参数作为数组传入。

function add(...numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

add(1, 2, 3, 4, 5); // 15

类型推论

类型推论(Type Inference)是 TypeScript 的一个重要特性,它能够根据你的代码自动判断出变量的类型。这使得你能够在很多情况下省略类型注解,让代码更加简洁。

1. 基本推论

如果在声明变量时就赋了一个初始值,TypeScript 能够自动推断出该变量的类型。

let num = 10;  // 类型推断为 number
let str = 'Hello';  // 类型推断为 string

2. 上下文推论

在某些情境下,TypeScript 也会根据上下文环境来推断类型,如事件处理器的参数:

window.addEventListener('click', (e) => {
  console.log(e.target);  // 类型推断 e 为 MouseEvent
});

3. 最佳通用类型

当需要从几个表达式中推断类型时,会尽可能地推断出一个最适合的类型。

let arr = [0, 1, null];  // 类型推断为 (number | null)[]

虽然类型推论可以省去许多类型注解的麻烦,但在编写复杂的程序时,显式的类型注解仍然是很有必要的,它可以帮助你更好地理解代码,并提前发现可能的错误。

由于 TypeScript 的类型推论特性,你不一定需要为每个变量显式地提供类型注解。TypeScript 会尝试分析你的代码,并根据其上下文推断出最可能的类型。这意味着在很多情况下,你可以省去手动添加类型注解的工作。

例如,当你直接给变量赋值时,TypeScript 会根据赋值推断出变量的类型:

let str = 'Hello';  // TypeScript 推断出 str 的类型为 string

但是,请注意,虽然类型推论能让编程变得更轻松,但如果你想确保类型安全,或者类型推论无法推断出正确的类型时,你仍然需要手动添加类型注解。例如,函数参数的类型通常需要显式指定,以确保函数使用的正确性。

function greet(name: string) {
  console.log(`Hello, ${name}`);
}

总的来说,你可以根据自己的需求以及项目的复杂程度,来决定何时使用类型注解,何时利用类型推论。

接口

在 TypeScript 中,接口的作用是为这些类型命名和为你的代码或第三方代码定义契约。

基本的接口示例:

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);

在上述代码中,我们定义了一个名为 LabelledValue 的接口,它有一个 label 属性,类型为 string。然后我们有一个 printLabel 函数,其参数 labelledObj 是 LabelledValue 类型。这意味着任何传递给 printLabel 的对象,只要它包含一个 label 属性且类型为 string,那么它就是一个有效的 LabelledValue 对象。

在最后一行,我们创建了一个对象 myObj,它有一个 label 属性和一个 size 属性。但当我们将 myObj 传递给 printLabel 函数时,TypeScript 只会检查它是否符合 LabelledValue 接口的结构,即只检查 label 属性,其余的属性会被忽视。

注意:

  • 在定义接口时,不能使用=赋值符号,应该使用关键字interface
  • 如果想要定义一个接口来表示对象数组,应该首先定义对象的接口,然后使用该接口定义数组

示例:

// 不能使用`=`赋值符号
interface Item {
  name: string;
  age: number;
  isPerson: boolean;
}

// Item[]定义数组
function test(data: Item[]) {
  data.forEach(i => {
    console.log(i.name);
    console.log(i.age);
    console.log(i.isPerson);
  });
}

const data = [
  {
    name: '张三',
    age: 18,
    isPerson: true,
    sex: '男'
  },
  {
    name: '李四',
    age: 20,
    isPerson: false,
    sex: '女'
  }
];

test(data);

1. 可选属性

在接口中,我们不仅可以定义必需的属性,还可以定义可选属性。这是通过在属性名后添加问号 ? 来实现的。

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

let tom: Person = {
  name: "Tom"
};

在这个例子中,age 是一个可选属性,这意味着 Person 类型的对象可以没有 age 属性。

2. 任意属性

有时候我们希望一个接口允许有任意数量的属性,可以使用如下方式定义:

interface Person {
  name: string;
  [propName: string]: any;
}

let tom: Person = {
  name: "Tom",
  gender: "male",
  age: 25
};

在这个例子中,Person 接口有一个任意属性 [propName: string],表示 Person 对象可以有任意数量的属性。

3. 只读属性

有些时候,你可能希望对象的一些字段只能在创建的时候被赋值,这时你可以使用 readonly 来定义只读属性:

interface Person {
  readonly name: string;
}

let tom: Person = {
  name: "Tom"
};

tom.name = "Jerry"; // Error: name is read-only

在这个例子中,name 是一个只读属性,尝试修改它的值会导致错误。

4. 额外的属性检查

在 TypeScript 中,当我们将一个对象直接赋值给一个接口类型的变量时,TypeScript 会进行严格的属性检查,如果对象中存在接口中没有定义的属性,编译器就会报错。

例如:

interface Config {
    color?: string;
    width?: number;
}

function createSquare(config: Config): void {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

在这段代码中,colour 属性在接口 Config 中并没有定义,所以 TypeScript 会提示错误。

但在实际开发中,我们有时候希望对象可以有额外的属性。这时,我们可以使用索引签名来满足需求:

interface Config {
    color?: string;
    width?: number;
    [propName: string]: any;
}

function createSquare(config: Config): void {
    // ...
}

let mySquare = createSquare({ colour: "red", width: 100 });

现在,Config 接口允许拥有任意名字的属性,只要它们的值是 any 类型。在这种情况下,上述代码就不会有编译错误了。

另外,另一种解决方法是使用类型断言:

let mySquare = createSquare({ colour: "red", width: 100 } as Config);

使用类型断言,可以明确告诉 TypeScript,你知道这个对象的实际类型,所以 TypeScript 就不再进行额外属性的检查了。

5. 函数类型

我们可以用接口定义函数需要遵守的规则。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  return source.search(subString) !== -1;
}

6. 类类型

TypeScript 也可以通过接口明确类要实现的特定的行为。

interface ClockInterface {
  currentTime: Date;
  setTime(d: Date): void;
}

class Clock implements ClockInterface {
  currentTime: Date = new Date();
  setTime(d: Date) {
    this.currentTime = d;
  }
}

7. 索引类型

我们还可以使用索引类型来描述我们可以用某种特定的方式来索引类,如通过数字或字符串。

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0];

8. 接口的继承

接口可以像类一样继承,可以使一个接口继承多个接口。

interface Shape {
  color: string;
}

interface PenStroke {
  penWidth: number;
}

interface Square extends Shape, PenStroke {
  sideLength: number;
}

let square = {} as Square;
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

在上面的例子中,Square 接口继承了 Shape 和 PenStroke 两个接口,因此创建 Square 对象时,必须包含这两个接口中定义的所有属性。

中级部分

在 TypeScript 中,类有三个主要元素:状态、行为和构造函数。状态是对象的属性,行为是它的方法,而构造函数是在创建对象时初始化这些属性的方法。

例如,我们可以创建一个“Person”类,如下:

class Person {
  name: string; // State
  age: number; // State

  // Constructor
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // Behavior
  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

1. 继承

在TypeScript中,我们可以使用extends关键字实现继承。子类会继承父类的属性和方法。我们还可以使用super关键字来调用父类的构造函数和方法。

class Employee extends Person {
  salary: number;

  constructor(name: string, age: number, salary: number) {
    super(name, age); // Calling the parent constructor
    this.salary = salary;
  }

  work() {
    console.log(`${this.name} is working.`);
  }
}

2. 静态属性

在 TypeScript 中,我们可以使用 static 关键字定义类的静态成员。这些成员可以直接通过类名而不是实例来访问。

class MyClass {
  static staticProperty = "Some value";

  static staticMethod() {
    console.log("This is a static method");
  }
}

console.log(MyClass.staticProperty); // "Some value"
MyClass.staticMethod(); // "This is a static method"

3. 存取器

TypeScript 支持通过 getter 和 setter 方法访问和设置类的属性。这些方法可以在类定义中使用 get 和 set 关键字进行声明。

class MyClass {
  private _name: string;

  get name(): string {
    return this._name;
  }

  set name(newName: string) {
    this._name = newName;
  }
}

4. 抽象类

TypeScript 的抽象类是不能直接实例化的类。这些类通常被其他类继承,并且可能包含一定数量的抽象方法。这些方法必须在派生类中被实现。

abstract class AbstractClass {
  abstract doSomething(): void;
}

class DerivedClass extends AbstractClass {
  doSomething() {
    console.log("Doing something...");
  }
}

函数

1. 函数重载

TypeScript 还支持函数重载,这意味着你可以根据传入参数的不同,让同一个函数有不同的行为。

function greet(name: string): string;
function greet(age: number): string;

function greet(nameOrAge: any): string {
  if (typeof nameOrAge === 'string') {
    return `Hello, ${nameOrAge}`;
  } else {
    return `Hello, age ${nameOrAge}`;
  }
}

2. 泛型函数

泛型函数是可以处理不同类型数据的函数。在函数中,你可以使用类型变量,这种类型变量在函数被调用时才会被具体的类型替代。

function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>("myString");

在上述代码中,T 是类型变量,你可以把它看作是类型的占位符。在函数被调用时,T 会被实际的类型替代(在这个例子中是 string)。

泛型

泛型是 TypeScript 的一种工具,它创建可重用的组件,而不是单一类型的组件,从而提供了更大的灵活性。泛型的主要目标是提供类型安全的方式,同时保持最大的功能。

1. 基础的泛型

泛型最常见的例子可能是数组。在 TypeScript 中,我们可以使用泛型来定义一个数组:

let list: Array<number> = [1, 2, 3];

在这个例子中,我们使用了泛型 Array,其中 T 是此数组将包含的元素的类型。

2. 泛型函数

泛型不仅可以用于声明类和接口,也可以用于函数。泛型函数可以适应任何类型:

function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>("myString");  // type of output will be 'string'

泛型类型: 你还可以创建自己的泛型类型。例如,以下是与上面 identity 函数相同的例子,但这次我们将其定义为泛型类型:

let myIdentity: <T>(arg: T) => T = identity;

3. 泛型类

类也可以具有泛型参数。例如,一个简单的泛型类可能是:

class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

在上述代码中,T 是类型变量,它帮助捕获用户提供的类型(例如 number),以便我们稍后可以使用这些类型。

4. 泛型约束

有时我们可能想要限制泛型能够接受的类型。在这种情况下,我们可以创建一个包含列出应该具有的属性的接口。这被称为泛型约束:

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);  // Now we know it has a .length property, so no more error
  return arg;
}

在这个例子中,我们使用 extends 关键字来表示泛型 T 应该遵守接口 Lengthwise 的形状。现在,如果 arg 不包含 length 属性,TypeScript 编译器将报错。

高级类型

1. 交叉类型

交叉类型是将多个类型合并为一个类型。这让我们可以将现有的多种类型叠加到一起得到所需的任意类型:

function extend<T, U>(first: T, second: U): T & U {
  let result = <T & U>{};
  for (let id in first) {
    (<any>result)[id] = (<any>first)[id];
  }
  for (let id in second) {
    if (!result.hasOwnProperty(id)) {
      (<any>result)[id] = (<any>second)[id];
    }
  }
  return result;
}

let x = extend({ a: "hello" }, { b: 42 });

2. 联合类型

联合类型表示一个值可以是几种类型之一。在使用联合类型时,我们只能访问此联合体的所有类型里共有的成员:

function padLeft(value: string, padding: string | number) {
  // ...
}

3. 类型别名

类型别名会给一个类型起个新名字。类型别名有时和接口很相似,但可以作用于原始值,联合类型,交叉类型等任何你需要手写的类型:

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

4. 字符串字面量类型

字符串字面量类型允许你指定字符串必须的固定值:

type Easing = "ease-in" | "ease-out" | "ease-in-out";

5. 数值字面量类型

数值字面量类型是 TypeScript 的一种特殊的子类型,它可以用于描述可辨识联合模式,也可以用于描述一些特定的数值:

type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6;

6. 可辨识联合

一种包含共同的字符串字面量属性的模式,TypeScript 可以对联合类型进行详细的类型推断:

interface Square {
  kind: "square";
  size: number;
}
interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}
interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Rectangle | Circle;

7. 映射类型

映射类型允许我们从旧类型中创建新类型:

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
}

type Partial<T> = {
  [P in keyof T]?: T[P];
}

枚举

枚举在 TypeScript 中允许我们定义一组命名的数值常量。枚举使用 enum 关键字来定义。

1. 数字枚举

这是最常见的枚举类型,它可以为一组数值赋予友好的名字。

enum Direction {
  Up = 1,
    Down,
    Left,
    Right
}

在这个例子中,我们定义了一个数字枚举,其中 Up 初始化为 1,而其他成员会自动增加。所以 Down 为 2,Left 为 3,以此类推。

2. 字符串枚举

在字符串枚举中,每个成员必须用字符串字面量或另一个字符串枚举成员进行初始化。

enum Direction {
  Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT"
}

3. 异构枚举

枚举可以混合字符串和数字成员,但不推荐这种方式。

enum BooleanLikeHeterogeneousEnum {
  No = 0,
    Yes = "YES",
}

4. 计算的和常量成员

每个枚举成员都有一个值,这个值可以是常量或计算出来的。

enum FileAccess {
  None,
    Read = 1 << 1,
    Write = 1 << 2,
    ReadWrite = Read | Write
}

5. 使用枚举

你可以通过枚举的属性访问任何枚举成员。

let direction = Direction.Up;

6. 反向映射

数字枚举具有反向映射,可以根据枚举值得到它的名字。

console.log(Direction[Direction.Up]); // 输出 "Up"

枚举在 TypeScript 中是一个非常有用的特性,当你有一组数值集合时,尤其是它们具有明确的名字时,使用枚举会更加清晰。

类型断言与类型保护

类型断言是你明确告诉编译器,你明确知道对象的实际更具体的类型。类型断言就好像其它语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。

在 TypeScript 中,你有两种方式可以做类型断言:

  1. 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
  1. as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

两种形式是等价的,使用哪种取决于你的选择,但在 JSX 语法中,只有 as 类型断言是被允许的。

类型保护 (Type Guards)

类型保护是执行运行时检查的一种方式,它也可以作为定义类型的一种方式。它们通常与类型断言一起使用。

例如,我们可以通过检查对象是否拥有某个属性,或者变量是否为某个类型,来缩小类型的范围:

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

在这个示例中,isFish 是一个类型保护,我们创建它来缩小 Fish 或 Bird 联合类型的范围。pet is Fish 是我们的类型谓词。谓词是 parameterName is Type 这种形式,parameterName 必须是来自当前函数签名里的一个参数名。

当我们对每一个 pet 使用 isFish 时,TypeScript 会在调用的那个代码块里把 pet 的类型缩小到 Fish。

进阶部分

模块

TypeScript 和 JavaScript ES6 都支持模块化编程,模块化可以帮助我们将代码拆分成可复用的单元,并控制这些单元之间的逻辑。模块在其自身的作用域里执行,而不是在全局作用域里,这意味着定义在一个模块里的变量、函数、类等在模块外部是不可见的,除非你明确地使用 export 形式之一导出它们。相反,如果想使用其他模块导出的变量、函数、类、接口等,你必须要导入它们,可以使用 import 形式之一。

// math.ts
export function add(a: number, b: number): number {
  return a + b;
}
// main.ts
import { add } from './math';

console.log(add(1, 2));  // 3

声明合并

当声明在同一个名字上,TypeScript 会将这些声明合并成单个声明。合并的声明必须是相同的类型 (变量, 函数, 命名空间, 类, 枚举, 接口, 类型别名)。

interface Box {
  height: number;
  width: number;
}

interface Box {
  scale: number;
}

let box: Box = {height: 5, width: 6, scale: 10};

在这个例子中,Box 接口在一个地方被定义了两次,但它们会被合并到一起并形成一个单一的接口。

装饰器

装饰器是一种特殊类型的声明,它可以被附加到类声明、方法、访问器、属性或参数上。装饰器使用 @expression 这种形式,expression 求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。

@sealed
  class Greeter {
    greeting: string;
    constructor(message: string) {
      this.greeting = message;
    }
    @enumerable(false)
    greet() {
      return "Hello, " + this.greeting;
    }
  }

在这个示例中,@sealed 和 @enumerable(false) 就是装饰器。

Mixins

Mixins 是多个类合并成一个类的方式。在 TypeScript 中,它可以使用交叉类型实现。

class Disposable {
  isDisposed: boolean;
  dispose() {
    // Dispose something
  }
}

class Activatable {
  isActive: boolean;
  activate() {
    // Activate something
  }
}

class SmartObject implements Disposable, Activatable {
  // This is a mixin, need to implement methods from `Disposable` and `Activatable`.
}

异步处理

TypeScript 支持现代的 JavaScript 异步处理,包括 Promise、async/await 等。

Promise 是异步处理的一种方式,它代表一个异步操作的最终完成 (或失败) 及其结果值。

let promise = new Promise((resolve, reject) => {
  // Do some async operation
});

async/await 是异步操作的一种方式,它使得异步代码的写法和同步代码类似。

async function foo() {
  let result = await someAsyncOperation();
}

工具类型

TypeScript 提供了一系列工具类型,你可以用它们进行特定的类型操作。例如,Partial、Required、Readonly、Record<K,T>、Pick<T, K>、Omit<T, K>、Exclude<T, U>、Extract<T, U>、NonNullable 等。它们可以让你在类型层面进行更精细的控制。