元组

元组是一个描述定长数组的类型,数组的各项可以类型不同。

考虑下面的 js 代码:

  1. const arr = ['I', 'l', 4, '514']

arr 的数组内含有 string 和 number,而且我们可以确定每个 index 的数据类型,index 是 0,1,3 时候是 string,2 时候是 number,所以它的类型是 [ string, string, number, string ]。
可以写为下述代码:

  1. const arr: [string, string, number, string] = ['I', 'l', 4, '514']
  2. arr[0] = '1' // OK
  3. arr[0] = 1 // Type '1' is not assignable to type 'string'.ts(2322)
  4. const arr2: ['I', 'l', 4, '514'] = ['I', 'l', 4, '514']
  5. const arr3: [unknown, unknown, unknown, string] = ['I', 'l', 4, '514']

由于存在子类型多态(包含多态),同一个值可以有多种类型,所以我们可以有多种对其声明的类型,比如 arr2 和 arr3。

元组是定长的,所以不同长度的元组是不兼容的。这是 ts 2.7 才引入的限制,如果需要变长的元组,请看 Fixed Length Tuples 这次改动的解释。

下面看一段新的代码:

  1. const arr: readonly [string, string, number, string] = ['I', 'l', 4, '514']
  2. arr[0] = '1' // Cannot assign to '0' because it is a read-only property.

这里我们添加了 readonly 修饰符 , 就能保证数据是只读的了。只读性对于用户写代码来说,是非常重要的特性。如果你以后没有修改的需求,请尽可能给数据加上 readonly 修饰符。

当然 readonly 这个词很长,所以我们可以考虑让类型推断自动帮我们加上。 写简洁清晰的 TypeScript 代码的诀窍就是,多用类型推断功能。如果需要推断出来 readonly 的元组,我们应该使用 const contexts for literal expressions :

  1. const arr = ['I', 'l', 4, '514'] as const; // const arr: readonly ["I", "l", 4, "514"]

数组

数组是一个变长的,每一项的类型都相同的列表,数组的写法如下

  1. const foo: Array<number> = [1, 2, 3];

其中 foo 的类型参数是 Array<number> , 这里的 Array 带一个尖括号,尖括号内的是类型参数。

我们可以这样子想象,Array 是一个类型空间(这里的空间指声明空间,声明空间是笔记一引入的概念)) 上的一个函数,而且是一个构造函数,它的作用是,接受一个 type,返回一个 type。用伪代码,我们可以写成:

  1. Array: (elementType: type) => type

上面的 Array<number> 中:
number 是一个类型,是一个 type, number 就是 elementType 这个形式参数的实际参数,
Array<number> 就是一个由 Array 这个一元 类型构造器 传入一个实际类型参数——也就是 number 后生成的新类型。

由于数组过于常用,或者为了和 C# 长得更像,数组有个另类的写法,比如:

  1. const foo: number[] = [1, 2, 3];

两种写法都是合法的。

我个人倾向于,在简单的使用常见中使用 [] ,在复杂的场景中(生成的数组类型还作为其他类型构造器的类型参数,或者 Array 的类型参数不是单纯的标识符)使用 Array<>

类型别名

A type alias declaration introduces a type alias in the containing declaration space.

用个比喻说,类型别名 是类型声明空间中的变量和函数。我下面用不专业的说法描述一下:

类型别名变量

类型别名第一个作用是当作类型中的一个变量(类似于 js 的 const 声明的变量)使用

  1. type ID = string;
  2. type Rank = number;
  3. const aliceId: ID = getId('alice');
  4. const bobId: ID = getId('bob');
  5. const aliceRank: Rank = getRank('alice');
  6. const bobRank: Rank = getRank('bob');

你给类型起了一个别名,如果 ID 要从 string 变成 number,或者自定义的 UUID 类,我们都只需要改一处就好。这是类型别名最初的用法。

类型别名函数

类型别名可以当作一个类型的函数使用,比如:

  1. type Pair<A, B> = [A, B];
  2. type List<T> = Array<T>;
  3. type Matrix<T> = List<List<T>>;
  4. type Foo<T> = []

类型别名是可以带类型参数的函数。接受一个或者多个类型,返回一个类型,而且我们的类型参数可以不被使用。