TypeScript

132 阅读10分钟

Visual Studio Code

支持代码调试、语法高亮、代码补全与代码重构等功能,是一款高度可定制化的集成开发环境,提供了定制主题样式、绑定功能快捷键等功能。它还具有高度的可扩展性,支持一套灵活的插件系统,方便开发者编写和安装功能强大的插件,例如ESLint和Live Share等。

快速开始

包含了实际开发中所必需的编码、编译和运行等主要步骤。

在线编写并运行TypeScript

TypeScript官方网站上提供了一个网页版的TypeScript代码编辑工具,如图2-1所示,它非常适合在学习TypeScript的过程中使用。该工具的优点是:

▪方便快捷,不需要进行安装和配置,只需要打开浏览器就可以开始编写代码并且拥有与集成开发环境相似的编码体验。

▪能够快速地切换TypeScript版本,并且提供了常用编译选项的可视化配置界面。

▪省去了手动编译和运行TypeScript代码的操作,只需单击相应按钮即可编译和运行代码。

打开网页版的TypeScript编辑器,地址为www.typescriptlang.org/play

npm

npm(Node Package Manager)是2009年发布的开源项目,它是Node.js默认的包管理器,用于帮助JavaScript开发者方便地分享代码。“npm注册表”(Registry)是npm的组成部分之一,它是一个在线的仓库,用于存放Node.js代码包。npm还提供了一个命令行工具,开发者可以通过它方便地安装和发布代码包。

nuget

NuGet是一个免费并且开源的.NET包管理器,作为Visual Studio的扩展随着Visual Studio 2012第一次发布。通过NuGet客户端工具,开发者能够方便地发布和安装代码包。

本地安装:TypeScript

虽然Visual Studio Code支持编写TypeScript语言的程序,但是它并没有内置TypeScript编译器。为了能够编译TypeScript程序,我们还需要单独安装TypeScript语言。

安装TypeScript语言最简单的方式是使用npm工具。如果你的计算机中还没有安装Node.js,则需要到Node.js的官网并安装。在安装Node.js的同时,也会自动安装npm工具。安装完成后,可使用如下命令来验证安装是否成功:

node -v

若安装成功,运行上面的命令会输出Node.js的版本号,如“12.13.0”。

接下来,使用下面的命令全局安装TypeScript:

npm install -g typescript

“npm install”是npm命令行工具提供的命令。

当安装完成后,可以使用下面的命令来验证TypeScript是否安装成功:

tsc --version

若安装成功,则运行该命令的结果可能会显示“Version 3.x.x”,该数字表示安装的TypeScript的版本号。

Node.js的官网地址为nodejs.org/en/。

[2] LTS(长期支持)是软件生命周期管理策略的一种,它的维护周期比一般版本的维护周期要长一些。

[3] npm安装命令说明:docs.npmjs.com/cli/install…

创建文件

首先,新建一个名为sample的目录作为示例项目的根目录,所有代码都将放在这个目录下。接下来,启动Visual Studio Code,然后将sample文件夹拖曳到Visual Studio Code窗口中。

在Visual Studio Code中打开文件夹sample目录下新建tsconfig.json文件

并输入以下代码:

{

	"compilerOptions": {

			"strict": true,

			"target": "es5"

		}

	}
  tsconfig.json是TypeScript编译器默认使用的配置文件。此例中的配置文件启用了所有的严格类型编译程序编译选项,并将输出JavaScript的版本指定为ECMAScript 5

新建hello-world.ts文件

输入以下代码:

const greeting = 'hello, world';

console.log(greeting);

文件保存为“hello-world.ts”,TypeScript源文件的常规扩展名为“.ts”。

编译程序

Visual Studio Code的任务管理器已经集成了对TypeScript编译器的支持,我们可以利用它来编译TypeScript程序。

遇到错误:

它能将编译过程中遇到的错误和警告信息显示在“Problems”面板里。

使用快捷键“Ctrl + Shift + B”或从菜单栏里选择“Terminal→Run Build Task”来打开并运行构建任务面板,然后再选择“tsc: build - tsconfig.json”来编译TypeScript程序,如图2-9所示。

当编译完成后,在“hello-world.ts”文件的目录下会生成一个同名的“hello-world.js”文件,它就是编译输出的JavaScript程序。

此时的目录结构如下所示:

sample

  |-- hello-world.js

  |-- hello-world.ts

  `-- tsconfig.json

运行程序

在Visual Studio Code里,使用“Ctrl + ”(“”为反引号,位于键盘上数字键1的左侧)快捷键打开命令行窗口。

然后,使用Node.js命令行工具来运行“hello-world.js”,示例如下:

node hello-world.js

非严格类型检查

非严格类型检查是TypeScript默认的类型检查模式。在该模式下,类型检查的规则相对宽松。例如,在非严格类型检查模式下不会对undefined值和null值做过多限制,允许将undefined值和null值赋值给string类型的变量。当进行JavaScript代码到TypeScript代码的迁移工作时,非严格类型检查是一个不错的选择,因为它能够让我们快速地完成迁移工作。

严格类型检查

严格类型检查模式下不允许将undefined值如果使用本地开发环境,那么可以在工程的tsconfig.json配置文件中启用“--strict”编译选项。示例如下:

{
    "compilerOptions": {
        "strict": true,
    }
}

此例中,将“--strict”编译选项设置为true将开启所有的严格类型检查编译选项。

语法

顶端类型

在类型系统中,所有类型都是顶端类型的子类型。顶端类型涵盖了类型系统中所有可能的值。
TypeScript中有以下两种顶端类型:
▪any
▪unknown

any
any类型是从TypeScript 1.0开始就支持的一种顶端类型
在程序中,我们使用any类型来跳过编译器的类型检查。如果声明了某个值的类型为any类型,那么就相当于告诉编译器:“不要对这个值进行类型检查。

let x: any;
x = true;
x = 'hi';
x = 3.14;
x = 99999n;
x = Symbol();
x = undefined;
x = null;
x = {};
x = [];
x = function () {};

unknown

unknown类型是比any类型更安全的顶端类型,因为unknown类型只允许赋值给any类型和unknown类型,而不允许赋值给任何其他类型,该行为与any类型是不同的。示例如下:

    let x: unknown;

正确

    const a1: any = x;
    const b1: unknown = x;
    

错误

    const a2: boolean = x;
    const b2: string = x;
    const c2: number = x;
    const d2: bigint = x;
    const e2: symbol = x;
    const f2: undefined = x;
    const g2: null = x;

never

never类型是所有其他类型的子类型。所以,never类型允许赋值给任何类型,尽管并不存在never类型的值。示例如下:

    let x: never;
    let a: boolean = x;
    let b: string = x;
    let c: number = x;
    let d: bigint = x;
    let e: symbol = x;
    let f: void = x;
    let g: undefined = x;
    let h: null = x;
    

never在类型系统中位于类型结构的最底层,没有类型是never类型的子类型。因此,除never类型自身外,所有其他类型都不能够赋值给never类型。

数组类型定义

TypeScript提供了以下两种方式来定义数组类型:
▪简便数组类型表示法。
▪泛型数组类型表示法。
以上两种数组类型定义方式仅在编码风格上有所区别,两者在功能上没有任何差别。

简便数组类型表示法。

    let a: string[];
    let b: HTMLButtonElement[];

泛型数组类型表示法。

    let a: Array<string | number>;
    let b: Array<{ x: number; y: number }>;

readonly 只读属性

简便数组类型表示法
let a :readonly[ string ] = [''111'']

注意:
泛型不能使用readolny这个字段,可以使用下面字段

可以使用下面字段。
ReadonlyArray
“ReadonlyArray”类型专门用于定义只读数组。在该类型中,类型参数T表示数组元素的类型。示例如下:
const red: ReadonlyArray = [255, 0, 0];

Readonly也是
const red: Readonly = [255, 0, 0];

注意事项:
数组元素索引来访问只读数组元素,但是不能修改只读数组元素。

元组类型

   元组类型是数组类型的子类型。元组是长度固定的数组,并且元组中每个元素都有确定的类型。

示例:

let point: [number, number];
point = [0,3];
point = [0, 'y']; // 编译错误
point = ['x', 0]; // 编译错误
point = ['x', 'y']; // 编译错误

元组类型中的可选元素

   [T0?, T1?, ..., Tn?]
    
    let tuple: [boolean, string?, number?] = [true, 'yes', 1];
    tuple = [true];
    tuple = [true, 'yes'];
    tuple = [true, 'yes', 1];

元组类型中的剩余元素

const tuple: [number, ...string[]] = [0, 'a', 'b'];

元组的长度
function f(point: [number, number]) {
// 编译器推断出length的类型为数字字面量类型2
const length = point.length;

if (length === 3) { // 编译错误!条件表达式永远为 false
   }
}

对象

属性签名

属性签名声明了对象类型中属性成员的名称和类型。它的语法如下所示:

{
    PropertyName: Type;
}

可选属性

在属性签名中的属性名之后添加一个问号“?”,那么将定义一个可选属性。定义可选属性成员的语法如下所示: { PropertyName?: Type; }

在“--strictNullChecks”模式下,TypeScript会自动在可选属性的类型定义中添加unde-fined类型。

`js

{ x: number; y: number; z?: number; };

// 等同于:
{
    x: number;
    y: number;
    z?: number | undefined;
};

`

只读属性

在属性签名定义中添加readonly修饰符能够定义对象只读属性。定义只读属性的语法如下所示:

{ readonly PropertyName: Type; }

空对象类型字面量

空对象类型字面量表示不带有任何属性的对象类型,因此不允许在“{}”类型上访问任何自定义属性

const point: {} = { x: 0, y: 0 }; 
point.x;

//    ~
//    编译错误!属性 'x' 不存在于类型 '{}'

point.y;

 //    ~
 //    编译错误!属性 'y' 不存在于类型 '{}'

多余属性检查

多余属性检查能够带来的最直接的帮助是发现属性名的拼写错误

允许多余属性

  1. 使用类型断言

无编译错误

  const p0: { x: number } = { x: 0, y: 0 } as { x: number };

无编译错误

  const p1: { x: number } = { x: 0, y: 0 } as { x: 0; y: 0 };

2. 启用“--suppressExcessPropertyErrors”编译选项。

{
      "compilerOptions": {
        "suppressExcessPropertyErrors": true
     }
}

3. 使用“// @ts-ignore”注释指令。

该注释指令能够禁用针对某一行代码的类型检查。

//@ts-ignore

const point: { x: number } = { x: 0, y: 0 };

4. 为目标对象类型添加索引签名。

若目标对象类型上存在索引签名,那么目标对象可以接受任意属性,因此也就谈不上多余属性。

   const point: {
   x: number;
  [prop: string]: number; // 索引签名
  } = { x: 0, y: 0 };

函数类型

参数类型、返回值类型、this类型以及函数重载等。

常规参数类型 在函数形式参数列表中,为参数添加类型注解就能够定义参数的类型。

function add(x: number, y: number) { return x + y; }

可选参数类型

在JavaScript中,函数的每一个参数都是可选参数,而在TypeScript中,默认情况下函数的每一个参数都是必选参数。

 function add(x: number, y?: number) {
    return x + (y ?? 0);
 }

剩余参数

function add(x: number = 0, y: number) {
    return x + y;
 }
 add(1);            // 编译错误
 add(1, 2);         // 正确
add(undefined, 2); // 正确

数组类型的剩余参数

最常见的做法是将剩余参数的类型声明为数组类型

 function f(...args: number[]) {}

元组类型的剩余参数

剩余参数的类型也可以定义为元组类型

function f(...args: [boolean, number]) {}

解构参数类型

解构还可以应用在函数参数列表中。示例如下:

function f0([x, y]) {}
f0([0, 1]);
function f1({ x, y }) {}
f1({ x: 0, y: 1 });

我们可以使用类型注解为解构参数添加类型信息。示例如下:

function f0([x, y]: [number, number]) {}
f0([0, 1]);
function f1({ x, y }: { x: number; y: number }) {}
f1({ x: 0, y: 1 });

返回值类型

返回值类型为void,那么该函数只能返回undefined值。

f0和f1是正确的使用场景

 function f0(): void {
     return undefined;
}
function f1(): void {}

如果没有启用“--strictNullChecks”编译选项,那么void/**
  * --strictNullChecks=false
  */

function f0(): void {
     return null;
}

函数类型字面量

构造签名

 let Dog: { new (name: string): object };
 Dog = class {
   private name: string;
    constructor(name: string) {
       this.name = name;
    }
};

调用签名与构造签名

语法:

{
   new (x: number): Number;  // <- 构造签名

   (x: number): number;      // <- 调用签名
}

declare const F: {
 new (x: number): Number;  // <- 构造签名/
     (x: number): number;      // <- 调用签名
};

作为普通函数调用

const a: number = F(1);

作为构造函数调用

const b: Number = new F(1);

函数的this

const a = (this: {} ): void { }

接口的继承

接口可以继承其他的对象类型,这相当于将继承的对象类型中的类型成员复制到当前接口中。接口可以继承的对象类型如下:

▪接口。
▪对象类型的类型别名。
▪类。
▪对象类型的交叉类型。

接口的继承需要使用extends关键字 interface Shape { name: string; }

 interface Circle extends Shape {
     radius: number;
 }

一个接口可以同时继承多个接口,父接口名之间使用逗号分隔。 下例中 ,Circle接口同时继承了Style接口和Shape接口:

 interface Style {
     color: string;
}

interface Shape {
    name: string;
 }

 interface Circle extends Style, Shape {
     radius: number;
 }

子接口与父接口中的同名类型成员必须是类型兼容的。

interface Circle extends Style, Shape {
    name: 'circle';

     color: number;
//  ~~~~~~~~~~~~~
 //  编译错误:'color' 类型不兼容,
//  'number' 类型不能赋值给 'string' 类型
 }

Circle接口中定义的draw方法一定要与所有父接口中的draw方法是类型兼容的

interface Shape {
     draw(): { x: number; y: number };
}

interface Circle extends Style, Shape {
    draw(): { color: string; x: number; y: number };
 }

类型别名

接口声明能够为对象类型命名,类型别名声明则能够为TypeScript中的任意类型命名。

语法: type AliasName = Type;

  type Point = {x: number, y: number}

递归的类型别名

type T = T
// 错误

可以写的递归类型

type T0 = { name: T0 };
type T1 = () => T1;
type T2 = new() => T2

数组类型别名和元组类型

    type T0 = Array<T0>
    type T1 = T1[]
    type T3 = [number, T3]

泛型类

interface A { name:T; } type T0 = A classB { name: T | undefined; }

type T1 = B<T1>;