简介和基本用法

2024/4/8 TypeScript

# 简介

  • 概述
    • TypeScript可以看成是JavaScript的超集,主要提供了类型系统和对ES6的支持,所有JavaScript脚本都可以当作TypeScript脚本(但是可能会报错),此外它再增加了一些自己的语法。
  • 类型
    • 类型指一组具有相同特征的值。如果两个值具有某种共同的特征,就可以说它们属于同一种类型。一旦确定某个值的类型,就意味着这个值具有该类型的所有特征,可以进行该类型的所有运算。凡是适用该类型的地方,都可以使用这个值;凡是不适用该类型的地方,使用这个值都会报错。
    • TypeScript 是在开发阶段报错,这样有利于提早发现错误,避免使用时报错。另一方面,函数定义里面加入类型,具有提示作用,可以告诉开发者这个函数怎么用。
  • 动态类型与静态类型
    • 动态类型在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript是一门解释型语言,没有编译阶段,所以它是动态类型,不具有很强的约束性。这对于提前发现代码错误非常不利。
// 例一
let x = 1; //值的类型是数值
x = 'hello'; //但是后面可以改成字符串
// 例二
let y = { foo: 1 };
delete y.foo; //这个属性是可以删掉的
y.bar = 2; //还可以新增其他属性
1
2
3
4
5
6
7
  • 静态类型在编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 它是静态类型。有利于代码的静态分析、代码重构、发现错误、做到语法提示和自动补全、提供代码文档。

● 是添加了类型系统的 JavaScript,适用于任何规模的项目。 ● 是一门静态类型、弱类型的语言。 ● 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。 ● 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。 ● 拥有很多编译选项,类型检查的严格程度由你决定。 ● 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。 ● 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。 ● 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。 ● 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。

# 基本用法

# 类型声明

​ TypeScript 代码最明显的特征,就是为 JavaScript 变量加上了类型声明。类型声明的写法,一律为在标识符后面添加“冒号 + 类型”。函数参数和返回值,也是这样来声明类型。

注意:

  • 变量的值应该与声明的类型一致,如果不一致,TypeScript 就会报错。
  • 变量只有赋值后才能使用,否则就会报错。
let foo:string;
console.log(x) // 报错
let foo:string = 123; // 报错
function toString(num:number):string {
  return String(num);
}
1
2
3
4
5
6

​ 类型声明是可选的,可以加,也可以不加。即使不加类型声明,依然是有效的 TypeScript 代码,只是这时不能保证 TypeScript 会正确推断出类型。

# 类型推断

​ 类型声明并不是必需的,TypeScript会在没有明确的指定类型的时候推断出一个类型。后面,如果变量更改为其他类型的值,跟推断的类型不一致,TypeScript 就会报错。TypeScript也可以推断函数的返回值,正因如此,所以函数返回值的类型通常是省略不写的。

​ 将以前的 JavaScript 项目改为 TypeScript 项目时,可以逐步地为老代码添加类型,即使有些代码没有添加,也不会无法运行。

​ 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查。

# TypeScript 的编译

​ JavaScript 的运行环境(浏览器和 Node.js)不认识 TypeScript 代码。因此,TypeScript 项目要想运行,必须先转为 JavaScript 代码,这个代码转换的过程就叫做“编译”(compile)。

​ TypeScript 官方没有做运行环境,只提供编译器。编译时,会将类型声明和类型相关的代码全部删除,只留下能运行的 JavaScript 代码,并且不会改变 JavaScript 的运行结果。

​ 因此,TypeScript 的类型检查只是编译时的类型检查,而不是运行时的类型检查。一旦代码编译为 JavaScript,运行时就不再检查类型了。

# 值与类型

​ “类型”是针对“值”的,可以视为是后者的一个元属性。每一个值在 TypeScript 里面都是有类型的。比如,3是一个值,它的类型是number。

​ TypeScript 代码只涉及类型,不涉及值。所有跟“值”相关的处理,都由 JavaScript 完成。

​ TypeScript 项目里面,其实存在两种代码,一种是底层的“值代码”( JavaScript 语法),另一种是上层的“类型代码”(TypeScript 的类型语法)。它们是可以分离的,TypeScript 的编译过程,实际上就是把“类型代码”全部拿掉,只保留“值代码”。

​ 编写 TypeScript 项目时,不要混淆哪些是值代码,哪些是类型代码。

# TypeScript Playground

​ 最简单的 TypeScript 使用方法,就是使用官网的在线编译页面,叫做 TypeScript Playground。

​ 只要打开这个网页,把 TypeScript 代码贴进文本框,它就会在当前页面自动编译出 JavaScript 代码,还可以在浏览器执行编译产物。如果编译报错,它也会给出详细的报错信息。这个页面还具有支持完整的 IDE 支持,可以自动语法提示。此外,它支持把代码片段和编译器设置保存成 URL,分享给他人。

# tsc 编译器

​ TypeScript 官方提供的编译器叫做 tsc,可以将 TypeScript 脚本编译成 JavaScript 脚本。本机想要编译 TypeScript 代码,必须安装 tsc。根据约定,TypeScript 脚本文件使用.ts后缀名,JavaScript 脚本文件使用.js后缀名。tsc 的作用就是把.ts脚本转变成.js脚本。

# 安装

$ npm install -g typescript # 全局安装。tsc是一个npm模块,必须先安装npm。也可安装为一个依赖模块
# 或者 tsc --version
$ tsc -v # 检查一下是否安装成功
#Version 5.1.6
1
2
3
4

# 帮助信息

$ tsc -h # -h或--help参数输出帮助信息
$ tsc --all # 查看完整的帮助信息
1
2

# 编译脚本

$ tsc app.ts # tsc命令后面,加上TypeScript脚本文件,就可以将其编译成JavaScript脚本
$ tsc file1.ts file2.ts file3.ts # 也可以一次编译多个TypeScript脚本
1
2

​ tsc 有很多参数,可以调整编译行为。

  • --outFile:将多个 TypeScript 脚本编译成一个 JavaScript 文件。
  • --outDir:指定将编译结果保存到其他目录(默认都保存在当前目录)。
  • --target:指定编译后的JavaScript版本。建议使用es2015,或者更新版本。
$ tsc file1.ts file2.ts --outFile app.js # 将多个 TypeScript 脚本编译成一个 JavaScript 文件
$ tsc app.ts --outDir dist # 指定将编译结果保存到其他目录(默认都保存在当前目录)
$ tsc --target es2015 app.ts # 指定编译后的JavaScript版本
1
2
3

# 编译错误的处理

​ 编译过程中,如果没有报错,tsc命令不会有任何显示;如果编译报错,tsc命令就会显示报错信息,但是这种情况下,依然会编译生成 JavaScript 脚本。因为 TypeScript 团队认为,编译器的作用只是给出编译错误,至于怎么处理这些错误,那就是开发者自己的判断了。开发者更了解自己的代码,所以不管怎样,编译产物都会生成,让开发者决定下一步怎么处理。

# 如果希望一旦报错就停止编译,不生成编译产物,可以使用--noEmitOnError参数。
$ tsc --noEmitOnError app.ts
# 只检查类型是否正确,不生成 JavaScript 文件,使用--noEmit参数
$ tsc --noEmit app.ts
1
2
3
4

# tsconfig.json

​ TypeScript 允许将tsc的编译参数,写在配置文件tsconfig.json。只要当前目录有这个文件,tsc就会自动读取,所以运行时可以不写参数。编译时直接调用tsc命令就可以了。

$ tsc file1.ts file2.ts --outFile dist/app.js
# 上面这个命令写成tsconfig.json,就是下面这样
{
  "files": ["file1.ts", "file2.ts"],
  "compilerOptions": {
    "outFile": "dist/app.js"
  }
}
# 有了这个配置文件,编译时直接调用tsc命令就可以了
$ tsc
1
2
3
4
5
6
7
8
9
10

# ts-node 模块

​ ts-node 是一个非官方的 npm 模块,可以直接运行 TypeScript 代码。

$ npm install -g ts-node # 使用时,可以先全局安装它
$ ts-node script.ts # 安装后,就可以直接运行 TypeScript 脚本
$ npx ts-node script.ts # 如果不安装ts-node,也可以通过npx调用它来运行TypeScript脚本
# 如果执行ts-node命令不带有任何参数,它会提供一个TypeScript的命令行REPL运行环境,
# 可以在这个环境中逐行输入TypeScript代码,逐行执行。
$ ts-node
> const twice = (x:string) => x + x;
> twice('abc')
'abcabc'
> # 要退出这个REPL环境,可以按下Ctrl + d,或者输入.exit
1
2
3
4
5
6
7
8
9
10

# 3、any 类型、unknown 类型、never 类型

​ TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个。never是 TypeScript 的唯一一个底层类型,所有其他类型都包括了never。

# any 类型

# 基本含义

​ any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。变量类型一旦设为any,实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错,怎么使用都可以。

实际开发中,any类型主要适用以下两个场合。

(1)出于特殊原因,需要关闭某些变量的类型检查,就可以把该变量的类型设为any。

(2)为了适配以前老的 JavaScript 项目,让代码快速迁移到 TypeScript,可以把变量类型设为any。有些年代很久的大型 JavaScript 项目,尤其是别人的代码,很难为每一行适配正确的类型,这时为那些类型复杂的变量加上any,TypeScript 编译时就不会报错。

​ 从集合论的角度看,any类型可以看成是所有其他类型的全集,包含了一切可能的类型。TypeScript 将这种类型称为“顶层类型”,意为涵盖了所有下层。

# 类型推断问题

​ 对于开发者没有指定类型、TypeScript必须自己推断类型的那些变量,如果无法推断出类型,就会认为该变量的类型是any,以至于后面就不再对其进行类型检查了,怎么用都可以。对于那些类型不明显的变量,一定要显式声明类型,防止被推断为any。

​ TypeScript 提供了一个编译选项noImplicitAny,打开该选项,只要推断出any类型就会报错。

$ tsc --noImplicitAny app.ts
1

​ 即使开了noImplicitAny,使用let和var命令声明变量,但不赋值也不指定类型,是不会报错的。const命令没有这个问题,因为 JavaScript 语言规定const声明变量时,必须同时进行初始化(赋值)。因此,建议使用let和var声明变量时,如果不赋值,就一定要显式声明类型,否则可能存在安全隐患。

# 污染问题

​ any类型除了关闭类型检查,还有一个很大的问题,就是它会污染其他具有正确类型的变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。TypeScript也检查不出错误,问题留到运行时才会暴露。

# unknown 类型

​ 为了解决any类型污染其他变量的问题,TypeScript3.0引入了unknown类型。它与any含义相同,表示类型不确定,可能是任意类型,但它有一些使用限制,不像any那样自由。可以视为严格版的、更安全的any。一般来说,凡是需要设为any类型的地方,通常都应该优先考虑设为unknown类型。

(1)unknown跟any的相似之处,在于所有类型的值都可以分配给unknown类型。

(2)unknown类型跟any类型的不同之处,在于它不能直接使用。主要有以下几个限制。

  • unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)。
  • 不能直接调用unknown类型变量的方法和属性。
  • unknown类型变量能够进行的运算是有限的,只能进行比较运算(==、===、!=、!==、||、&&、?)、取反运算(!)、typeof运算符和instanceof运算符这几种,其他运算都会报错。
  • 只有经过“类型缩小”,unknown类型变量才可以使用。就是缩小unknown变量的类型范围,确保不会出错。只有明确unknown变量的实际类型,才允许使用它,防止像any那样可以随意乱用,污染其他变量。

​ 在集合论上,unknown也可以视为所有其他类型(除了any)的全集,所以它和any一样,也属于 TypeScript 的“顶层类型”。

# never 类型

​ 为了保持与集合论的对应关系,以及类型运算的完整性,TypeScript 还引入了“空类型”的概念,即该类型为空,不包含任何值。由于不存在任何属于空类型的值,所以该类型被称为never,即不可能有这样的值。变量的类型是never,就不可能赋给它任何值,否则都会报错。never类型的一个重要特点是,可以赋值给任意其他类型。

​ never类型的使用场景:

(1)主要是在一些类型运算之中,保证类型运算的完整性。

(2)不可能返回值的函数,返回值的类型就可以写成never。

(3)如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于never类型。

​ 在集合论上,空集是任何集合的子集。TypeScript 就相应规定,任何类型都包含了never类型。因此,never类型是任何其他类型所共有的,TypeScript 把这种情况称为“底层类型”。

上次更新: 2024/4/13 07:03:33