概览

TypeScript 类型系统

在讨论为什么使用 TypeScript 时,我们涵盖了 TypeScript 类型系统的主要功能。以下是讨论的一些关键点:

  • TypeScript 类型系统设计是可选的,因此,你的 JavaScript 即是 TypeScript;
  • TypeScript 不会阻止 JavaScript 的运行,即使存在类型错误也不例外,这能让你的 JavaScript 逐步迁移至 TypeScript。
    现在让我们开始学习 TypeScript 类型系统的语法,在这一章节中,你将能给你的代码加上类型注解,并且能看到它的益处。这将为我们进一步了解类型系统做铺垫。

基本注解

如前文所提及,类型注解使用 :TypeAnnotation 语法。类型声明空间中可用的任何内容都可以用作类型注解。

在下面这个例子中,使用了变量、函数参数以及函数返回值的类型注解:

  1. const num: number = 123;
  2. function identity(num: number): number {
  3. return num;
  4. }

原始类型

JavaScript 原始类型也同样适应于 TypeScript 的类型系统,因此 stringnumberboolean 也可以被用作类型注解:

  1. let num: number;
  2. let str: string;
  3. let bool: boolean;
  4. num = 123;
  5. num = 123.456;
  6. num = '123'; // Error
  7. str = '123';
  8. str = 123; // Error
  9. bool = true;
  10. bool = false;
  11. bool = 'false'; // Error

数组

TypeScript 为数组提供了专用的类型语法,因此你可以很轻易的注解数组。它使用后缀 [], 接着你可以根据需要补充任何有效的类型注解(如::boolean[])。它能让你安全的使用任何有关数组的操作,而且它也能防止一些类似于赋值错误类型给成员的行为。如下所示:

  1. const boolArray: boolean[];
  2. boolArray = [true, false];
  3. console.log(boolArray[0]); // true
  4. console.log(boolArray.length); // 2
  5. boolArray[1] = true;
  6. boolArray = [false, false];
  7. boolArray[0] = 'false'; // Error
  8. boolArray = 'false'; // Error
  9. boolArray = [true, 'false']; // Error

接口

接口是 TypeScript 的一个核心知识,它能合并众多类型声明至一个类型声明:

  1. interface Name {
  2. first: string;
  3. second: string;
  4. }
  5. let name: Name;
  6. name = {
  7. first: 'John',
  8. second: 'Doe'
  9. };
  10. name = {
  11. // Error: 'Second is missing'
  12. first: 'John'
  13. };
  14. name = {
  15. // Error: 'Second is the wrong type'
  16. first: 'John',
  17. second: 1337
  18. };

在这里,我们把类型注解:first: string + second: string 合并到了一个新的类型注解里 Name,这样能强制对每个成员进行类型检查。接口在 TypeScript 拥有强大的力量,在稍后,我们将会用整个章节来阐述如何更好的使用它。

内联类型注解

与创建一个接口不同,你可以使用内联注解语法注解任何内容::{ /Structure/ }

  1. let name: {
  2. first: string;
  3. second: string;
  4. };
  5. name = {
  6. first: 'John',
  7. second: 'Doe'
  8. };
  9. name = {
  10. // Error: 'Second is missing'
  11. first: 'John'
  12. };
  13. name = {
  14. // Error: 'Second is the wrong type'
  15. first: 'John',
  16. second: 1337
  17. };

内联类型能为你快速的提供一个类型注解。它可以帮助你省去为类型起名的麻烦(你可能会使用一个很糟糕的名称)。然而,如果你发现需要多次使用相同的内联注解时,考虑把它重构为一个接口(或者是 type alias,它会在接下来的部分提到)是一个不错的注意。

特殊类型

除了被提到的一些原始类型,在 TypeScript 中,还存在一些特殊的类型,它们是 anynullundefined 以及 void

any

any 类型在 TypeScript 类型系统中占有特殊的地位。它提供给你一个类型系统的「后门」,TypeScript 将会把类型检查关闭。在类型系统里 any 能够兼容所有的类型(包括它自己)。因此,所有类型都能被赋值给它,它也能被赋值给其他任何类型。以下有一个证明例子:

  1. let power: any;
  2. // 赋值任意类型
  3. power = '123';
  4. power = 123;
  5. // 它也兼容任何类型
  6. let num: number;
  7. power = num;
  8. num = power;

当你从 JavaScript 迁移至 TypeScript 时,你将会经常性使用 any。但你必须减少对它的依赖,因为你需要确保类型安全。当使用 any 时,你基本上是在告诉 TypeScript 编辑器不要进行任何的类型检查。

null 和 undefined

在类型系统中,JavaScript 中的 null 和 undefined 字面量和其他被标注了 any 类型的变量一样,都能被赋值给任意类型的变量,如下例子所示:

  1. let num: number;
  2. let str: string;
  3. // 这些类型能被赋予
  4. num = null;
  5. str = undefined;

void

使用 :void 来表示一个函数没有一个返回值

  1. function log(message: string): void {
  2. console.log(message);
  3. }

泛型

在计算机科学中,许多算法和数据结构并不会依赖于对象的实际类型。然而,你仍然会想在每个变量里强制提供约束。例如:在一个函数中,它接受一个列表,并且返回这个列表的反向排序,这里的约束是指传入至函数的参数与函数的返回值:

  1. function reverse<T>(items: T[]): T[] {
  2. const toreturn = [];
  3. for (let i = items.length - 1; i >= 0; i--) {
  4. toreturn.push(items[i]);
  5. }
  6. return toreturn;
  7. }
  8. const sample = [1, 2, 3];
  9. const reversed = reverse(sample);
  10. console.log(reversed); // 3, 2, 1
  11. // Safety
  12. reversed[0] = '1'; // Error
  13. reversed = ['1', '2']; // Error
  14. reversed[0] = 1; // ok
  15. reversed = [1, 2]; // ok

在上个例子中,函数 reverse 接受一个类型为 T(注意在 reverse<T> 中的类型参数) 的数组(items: T[]),返回值为类型 T 的一个数组(注意:T[]),函数 reverse 的返回值类型与它接受的参数的类型一样。当你传入 var sample = [1, 2, 3] 时,TypeScript 能推断出 reversenumber[] 类型,从而能给你类型安全。于此相似,当你传入一个类型为 string[] 类型的数组时,TypeScrip 能推断 reversestring[] 类型,如下例子所示:

  1. const strArr = ['1', '2'];
  2. let reversedStrs = reverse(strArr);
  3. reversedStrs = [1, 2]; // Error

事实上,JavaScript 数组已经拥有了 reverse 的方法,TypeScript 也确实使用了泛型来定义其结构:

  1. interface Array<T> {
  2. reverse(): T[];
  3. }

这意味着,当你在数组上调用 .reverse 方法时,得会获得类型安全:

  1. let numArr = [1, 2];
  2. let reversedNums = numArr.reverse();
  3. reversedNums = ['1', '2']; // Error

当在章节 环境声明 中提及了 lib.d.ts 时,我们会讨论更多关于 Array<T> 的信息。

联合类型

在 JavaScript 中,你希望属性为多种类型之一,如字符串或者数组。这就是联合类型所能派上用场的地方(它使用 | 作为标记,如 string | number)。在函数参数里。一个常见的用例是一个可以接受单个对象或对象数组的函数:

  1. function formatCommandline(command: string[] | string) {
  2. let line = '';
  3. if (typeof command === 'string') {
  4. line = command.trim();
  5. } else {
  6. line = command.join(' ').trim();
  7. }
  8. // Do stuff with line: string
  9. }

交叉类型

在 JavaScript 中, extend 是一种非常常见的模式,在这种模式中,你可以从两个对象中创建一个新对象,新对象会拥有着两个对象所有的功能。交叉类型可以让你安全的使用此种模式:

  1. function extend<T, U>(first: T, second: U): T & U {
  2. const result = <T & U>{};
  3. for (let id in first) {
  4. (<T>result)[id] = first[id];
  5. }
  6. for (let id in second) {
  7. if (!result.hasOwnProperty(id)) {
  8. (<U>result)[id] = second[id];
  9. }
  10. }
  11. return result;
  12. }
  13. const x = extend({ a: 'hello' }, { b: 42 });
  14. // 现在 x 拥有了 a 属性与 b 属性
  15. const a = x.a;
  16. const b = x.b;

元组类型

JavaScript 并没有支持类似于元组的支持。开发者通常只能使用数组来表示元组,但是 TypeScript 类型系统支持它。使用 :[typeofmember1, typeofmember2] 能够为元组添加类型注解,元组可以包含任意数量的成员,以下例子演示了元组:

  1. let nameNumber: [string, number];
  2. // Ok
  3. nameNumber = ['Jenny', 221345];
  4. // Error
  5. nameNumber = ['Jenny', '221345'];

将其与 TypeScript 中的解构一起使用:

  1. let nameNumber: [string, number];
  2. nameNumber = ['Jenny', 322134];
  3. const [name, num] = nameNumber;

类型别名

TypeScript 提供使用类型注解的便捷语法,你可以使用 type SomeName = someValidTypeAnnotation 的语法来创建别名:

  1. type StrOrNum = string | number;
  2. // 使用
  3. let sample: StrOrNum;
  4. sample = 123;
  5. sample = '123';
  6. // 会检查类型
  7. sample = true; // Error

不同于 interface 你可以提供一个类型别名至任意的类型注解上(在联合类型和交叉类型中比较实用),这有一些能让你熟悉语法的实例:

  1. type Text = string | { text: string };
  2. type Coordinates = [number, number];
  3. type Callback = (data: string) => void;

TIP

  • 如果你需要使用类型注解的层次结构,请使用接口。它能使用 implementsextends
  • 为一个简单的对象类型(像例子中的 Coordinates)使用类型别名,仅仅有一个语义化的作用。与此相似,当你想给一个联合类型和交叉类型使用一个语意化的名称时,一个类型别名将会是一个好的选择。

最后

现在你已经能够为你的大部分 JavaScript 代码添加类型注解,接着,让我们深入了解 TypeScript 的类型系统吧。

原文: https://jkchao.github.io/typescript-book-chinese/typings/overview.html