# TypeScript 基础

TypeScript 入门教程 (opens new window)

「1.8W字」2020不可多得的 TS 学习指南——阿宝哥 (opens new window)

Awesome TypeScript——阿宝哥 (opens new window)

# 1. 基础类型

# 1.1 never

never 类型表示的是那些永不存在的值的类型,可以利用 never 类型的特性来实现全面性检查。

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // 这里 foo 被收窄为 string 类型
  } else if (typeof foo === "number") {
    // 这里 foo 被收窄为 number 类型
  } else {
    // foo 在这里是 never
    const check: never = foo;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。因为 foo 的类型被定义为 string 或 number,永远不会走到 else 分支。

如果有一天,其它同事为 Foo 增加了 boolean 类型,且传入了 controlFlowAnalysisWithNever 函数,就会走到 else 分支,且把 boolean 类型的值赋值给了 never 类型,TypeScript 在编译时就会报错。通过这种方式,使函数总是确保覆盖了所有可能类型。

# 2. 高阶类型

# 2.1 联合类型

通过 | 运算符可以将现有的多种类型联合到一起成为一种类型,其示例包含了所有类型中的某一项。

# 字面量类型

约束取值只能是某几个值中的一个。

let num: 1 | 2 = 1;
let event:'click' | 'scroll' | 'mousemove' = 'click';
1
2

# 类型别名

可以为联合类型取一个别名:

type EventName = 'click' | 'scroll' | 'mousemove';
let event: EventName = 'scroll';
1
2

# 2.2 交叉类型

通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。

type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
type Point = PartialPointX & PartialPointY;

let point: Point = {
  x: 1,
  y: 1
}
1
2
3
4
5
6
7
8

# 同名属性的合并

在合并多个类型的过程中,刚好出现某些类型存在相同的成员,但对应的类型又不一致时,会分情况处理。

如果同名属性中某一个是基础类型,则合并后该属性类型为 never。

如果同名属性都是非基础数据类型,则该属性也会自动进行交叉合并。

# 2.3 使用接口定义类型

在面向对象语言中,接口是对行为的抽象,而具体行动需要由类去实现

TypeScript 中很重要的用途是定义对象的形状

interface Person {
  // 常规属性
  name: string;
  age: number;
  // 只读属性
  readonly id: string;
  // 可选属性
  age?: number;
  // 任意属性
  [propName: string]: any;
}
1
2
3
4
5
6
7
8
9
10
11

# 2.4 使用别名定义类型

类型别名用来给一个类型起个新名字。也可以用来定义对象的形状。

type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
type Person = {
  name: string;
  age: number;
}
1
2
3
4
5
6
7

# 2.5 type 与 interface 的区别

  1. 接口可以定义多次,会被自动合并为单个接口
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };
1
2
3
4
  1. 类可以实现 type 和 interface,但是不能实现 type 定义的联合类型
interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  x = 1;
  y = 2;
}
1
2
3
4
5
6
7
8
9
  1. 二者都可以被扩展,但方式不同

    • type 使用 & 扩展
    • interface 使用 extends 关键字
    • interface 可以扩展 type,type 不能扩展 interface
  2. interface 只用来定义对象或函数签名,type 可定义基础类型、联合类型等

// 定义基础类型
type Name = string;

// 定义联合类型
type PartialPoint = PartialPointX | PartialPointY;

// 定义元组
type Data = [number, string];
1
2
3
4
5
6
7
8

# 3. 函数

# 3.1 函数重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。

利用联合类型,我们可以这么实现:

function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}
1
2
3
4
5
6
7

然而这样有一个缺点,就是不能够精确的表达,输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。

这时,我们可以使用重载定义多个 reverse 的函数类型:

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}
1
2
3
4
5
6
7
8
9

上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。

TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。

# 4. 更多特性

# 4.1 类

class Greeter {
  // 静态属性
  static cname: string = "Greeter";
  // 成员属性
  greeting: string;
  // 私有属性
  #id: string;


  // 构造函数 - 执行初始化操作
  constructor(message: string) {
    this.greeting = message;
  }

  // 静态方法
  static getClassName() {
    return "Class name is Greeter";
  }

  // 成员方法
  greet() {
    return "Hello, " + this.greeting;
  }

  /* 访问器 */
  private _fullName: string; // 私有属性

  // getter 方法读取私有属性
  get fullName(): string {
    return this._fullName;
  }

  // setter 方法设置私有属性,可以添加拦截判断
  set fullName(newName: string) {
    if (passcode && passcode == "Hello TypeScript") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }

  /* 类中方法的重载(重载作用只有一个:提供 coding 过程中的代码提示) */
  getProducts(): void; // 方法签名 1
  getProducts(id: number): void; // 方法签名 2
  getProducts(id?: number) { // 方法的具体实现
    if(typeof id === 'number') {
        console.log(`获取id为 ${id} 的产品信息`);
    } else {
        console.log(`获取所有的产品信息`);
    }  
  }
}

let greeter = new Greeter("world");
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

# 4.2 断言

TypeScript 中有类型断言、非空断言、确定赋值断言。

这些断言归根到底是为了保证一些不符合 TypeScript 类型规则的代码,能够正常通过编译,但并不能保证代码能够正确执行。

function myFunc(maybeString: string | undefined | null) {
  // 传参为联合类型,调用时只使用某一种子类型的方法
  console.log(maybeString.length) // 编译报错
  console.log(maybeString!.length) // 编译通过
}
1
2
3
4
5

像上面这种情况,传参可能为 string、undefined、null 中的一个,函数体中调用只有 string 类型才有的 length 属性,TypeScript 检测到报错风险,编译报错。如果明确我们往这个函数传的参数就是 string 类型,就可以使用非空断言 maybeString! 过滤掉 undefined 和 null 类型。

但是,这种操作只能保证编译通过,如果在执行中传入了 null,代码会直接报运行时错误。如果要避免运行出错,还是要这么做:

console.log(maybeString?.length);
1

# 4.3 类型守卫

类型守卫是在变量类型不明确时,需要执行一些明确针对某些类型的操作时,通过某些运行时语句判断变量类型。这种判断主要思想有检测属性、原型、数据类型等。

  • in 关键字
  • typeof 关键字
  • instanceof 关键字
  • is 关键字
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}
1
2
3
4
5
6
7
8
9

# 4.4 声明文件

声明文件 —— TypeScript 入门教程 (opens new window)

声明文件是对所提供库函数的解释说明,通过它,ts 编译器才能可以实现对代码的补全、提示功能。

# 4.5 泛型

泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

# 4.6 装饰器

# 4.7 关于 __esModule

stackflow 中的解释 (opens new window) __esModule 的作用 (opens new window)

__esModule 是用来兼容 ES 模块导入 CommonJS 模块的默认导出方案。添加这个标识后,就能够使用 es 的模块化语法导入 CommonJS 模块。这种处理是由打包工具来实现的,如 webpack 或 typescript 编译器。

// commonjs-module.js
Object.defineProperty(exports, '__esModule', { value: true })
// ...
exports['default'] = cm;
1
2
3
4
// es.js
import cm from 'commonjs-module';
1
2

# 5. 工程化

# 5.1 tsconfig.json

# 5.2 工具集

TypeScript Playground (opens new window)

ESLint (opens new window)

TypeDoc (opens new window)

TypeScript to UML (opens new window)

最后更新时间: 9/18/2021, 10:23:54 AM