原始类型的类型标注

在 TypeScript 中原始类型对应的类型注解:

  1. const name: string = 'linbudu';
  2. const age: number = 24;
  3. const male: boolean = false;
  4. const undef: undefined = undefined;
  5. const nul: null = null;
  6. const obj: object = { name, age, male };
  7. const bigintVar1: bigint = 9007199254740991n;
  8. const bigintVar2: bigint = BigInt(9007199254740991);
  9. const symbolVar: symbol = Symbol('unique');

其中,除了 null 与 undefined 以外,余下的类型基本上可以完全对应到 JavaScript 中的数据类型概念,因此这里我们只对 null 与 undefined 展开介绍。

null 与 undefined

在 JavaScript 中,null 表示“这里有值,但是个空值”,undefined 表示“这里没有值”。
在 TypeScript 中,null 与 undefined 类型都是有具体意义的类型。也就是说,它们作为类型时,表示的是一个有意义的具体类型值。这两者在没有开启 strictNullChecks 检查的情况下,会被视作其他类型的子类型,比如 string 类型会被认为包含了 null 与 undefined 类型:

  1. const tmp1: null = null;
  2. const tmp2: undefined = undefined;
  3. const tmp3: string = null; // 仅在关闭 strictNullChecks 时成立,下同
  4. const tmp4: string = undefined;

void

JavaScript中,void 操作符会执行后面跟着的表达式并返回一个 undefined,void 操作符强制将后面的函数声明转化为了表达式。

  1. void function iife() {
  2. console.log("Invoked!");
  3. }();
  4. <a href="javascript:void(0)">清除缓存</a>

TypeScript当中,void 用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值

  1. function func1() {}
  2. function func2() {
  3. return;
  4. }
  5. function func3() {
  6. return undefined;
  7. }

数组的类型标注

在 TypeScript 中有两种方式来声明一个数组类型:

  1. const arr1: string[] = [];
  2. const arr2: Array<string> = [];

元组(Tuple)

如一个数组中只存放固定长度的变量,但我们进行了超出长度地访问:

  1. const arr3: string[] = ['lin', 'bu', 'du'];
  2. console.log(arr3[599]);

这是它并不会报错也不会有任何提示,显然是不符合预期的。因为我们能确定这个数组中只有三个成员,并希望在越界访问时给出类型报错。这时我们可以使用元组类型进行类型标注:

  1. const arr4: [string, string, string] = ['lin', 'bu', 'du'];
  2. console.log(arr4[599]);

image.png
此时将会产生一个类型错误:长度为“3”的元组类型“[string, string, string]”在索引“599“处没有元素。除了同类型的元素以外,元组内部也可以声明多个与其位置强绑定的,不同类型的元素:

  1. const arr5: [string, number, boolean] = ['linbudu', 599, true];

对数组合法边界内的索引访问(即 0、1、2)将精确地获得对应位置上的类型。同时元组也支持了在某一个位置上的可选成员:

  1. const arr6: [string, number?, boolean?] = ['linbudu'];
  2. // 下面这么写也可以
  3. // const arr6: [string, number?, boolean?] = ['linbudu', , ,];

对于标记为可选的成员,在 —strictNullCheckes 配置下会被视为一个 string | undefined 的类型。此时元组的长度属性也会发生变化,比如上面的元组 arr6 ,其长度的类型为 1 | 2 | 3:

  1. type TupleLength = typeof arr6.length; // 1 | 2 | 3

具名元组

你可能会觉得,元组的可读性实际上并不好。比如对于 [string, number, boolean]来说,你并不能直接知道这三个元素都代表什么,还不如使用对象的形式。

而在 TypeScript 4.0 中,有了具名元组(Labeled Tuple Elements)的支持,使得我们可以为元组中的元素打上类似属性的标记:

  1. const arr7: [name: string, age: number, male: boolean] = ['linbudu', 599, true];

具名元组可选元素的修饰符将成为以下形式:

  1. const arr7: [name: string, age: number, male?: boolean] = ['linbudu', 599, true];

解构赋值

除了显式地越界访问,还可能存在隐式地越界访问,如通过解构赋值的形式:

  1. const arr1: string[] = [];
  2. const [ele1, ele2, ...rest] = arr1;

对于数组,此时仍然无法检查出是否存在隐式访问,因为类型层面并不知道它到底有多少个元素。

但对于元组,隐式的越界访问也能够被揪出来给一个警告:

  1. const arr5: [string, number, boolean] = ['linbudu', 599, true];
  2. // 长度为 "3" 的元组类型 "[string, number, boolean]" 在索引 "3" 处没有元素。
  3. const [username, age, male, other] = arr5;

对象的类型标注

类似于数组类型,在 TypeScript 中我们也需要特殊的类型标注来描述对象类型,即 interface ,你可以理解为它代表了这个对象对外提供的接口结构。
首先我们使用 interface 声明一个结构,然后使用这个结构来作为一个对象的类型标注即可:

  1. interface IDescription {
  2. name: string;
  3. age: number;
  4. male: boolean;
  5. }
  6. const obj1: IDescription = {
  7. name: 'linbudu',
  8. age: 599,
  9. male: true,
  10. };

这里的“描述”指:

  • 每一个属性的值必须一一对应到接口的属性类型
  • 不能有多的属性,也不能有少的属性,包括直接在对象内部声明,或是 obj1.other = ‘xxx’ 这样属性访问赋值的形式

    修饰接口属性

    可选属性

    接口结构中同样通过 ? 来标记一个属性为可选: ```javascript interface IDescription { name: string; age: number; male?: boolean; func?: Function; }

const obj2: IDescription = { name: ‘linbudu’, age: 599, male: true, // 无需实现 func 也是合法的 };

  1. 在这种情况下,即使你在 obj2 中定义了 male 属性,但当你访问 obj2.male 时,它的类型仍然会是 boolean | undefined<br />假设新增一个可选的函数类型属性,然后进行调用:obj2.func() ,此时将会产生一个类型报错:_**不能调用可能是未定义的方法**_。但可选属性标记不会影响你对这个属性进行赋值,如:
  2. ```javascript
  3. obj2.male = false;
  4. obj2.func = () => {};

即使你对可选属性进行了赋值,TypeScript 仍然会使用接口的描述为准进行类型检查,你可以使用类型断言、非空断言或可选链解决

public

protected

readonly

它的作用是防止对象的属性被再次赋值

  1. interface IDescription {
  2. readonly name: string;
  3. age: number;
  4. }
  5. const obj3: IDescription = {
  6. name: 'linbudu',
  7. age: 599,
  8. };
  9. // 无法分配到 "name" ,因为它是只读属性
  10. obj3.name = "林不渡";

private

static

type 与 interface

interface 用来描述对象、类的结构
类型别名用来将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型

object、Object 以及 { }

在 TypeScript 中就表现为 Object 包含了所有的类型:

  1. // 对于 undefined、null、void 0 ,需要关闭 strictNullChecks
  2. const tmp1: Object = undefined;
  3. const tmp2: Object = null;
  4. const tmp3: Object = void 0;
  5. const tmp4: Object = 'linbudu';
  6. const tmp5: Object = 599;
  7. const tmp6: Object = { name: 'linbudu' };
  8. const tmp7: Object = () => {};
  9. const tmp8: Object = [];

和 Object 类似的还有 Boolean、Number、String、Symbol,这几个装箱类型(Boxed Types) 同样包含了一些超出预期的类型。以 String 为例,它同样包括 undefined、null、void,以及代表的 拆箱类型(Unboxed Types) string,但并不包括其他装箱类型对应的拆箱类型,如 boolean 与 基本对象类型,我们看以下的代码:

  1. const tmp9: String = undefined;
  2. const tmp10: String = null;
  3. const tmp11: String = void 0;
  4. const tmp12: String = 'linbudu';
  5. // 以下不成立,因为不是字符串类型的拆箱类型
  6. const tmp13: String = 599; // X
  7. const tmp14: String = { name: 'linbudu' }; // X
  8. const tmp15: String = () => {}; // X
  9. const tmp16: String = []; // X

object 的引入就是为了解决对 Object 类型的错误使用,它代表所有非原始类型的类型,即数组、对象与函数类型这些

  1. const tmp17: object = undefined;
  2. const tmp18: object = null;
  3. const tmp19: object = void 0;
  4. const tmp20: object = 'linbudu'; // X 不成立,值为原始类型
  5. const tmp21: object = 599; // X 不成立,值为原始类型
  6. const tmp22: object = { name: 'linbudu' };
  7. const tmp23: object = () => {};
  8. const tmp24: object = [];

最后是{},一个奇奇怪怪的空对象,如果你了解过字面量类型,可以认为{}就是一个对象字面量类型(对应到字符串字面量类型这样)。否则,你可以认为使用{}作为类型签名就是一个合法的,但内部无属性定义的空对象,这类似于 Object(想想 new Object()),它意味着任何非 null / undefined 的值:

  1. const tmp25: {} = undefined; // 仅在关闭 strictNullChecks 时成立,下同
  2. const tmp26: {} = null;
  3. const tmp27: {} = void 0; // void 0 等价于 undefined
  4. const tmp28: {} = 'linbudu';
  5. const tmp29: {} = 599;
  6. const tmp30: {} = { name: 'linbudu' };
  7. const tmp31: {} = () => {};
  8. const tmp32: {} = [];

虽然能够将其作为变量的类型,但你实际上无法对这个变量进行任何赋值操作

  1. const tmp30: {} = { name: 'linbudu' };
  2. tmp30.age = 18; // X 类型“{}”上不存在属性“age”。