
随着前端工程化的快速发展, TypeScript 变得越来越受欢迎,它已经成为前端开发人员必备技能。TypeScript 最初是由微软开发并开源的一种编程语言,自2012年10月发布首个公开版本以来,它已得到了人们的广泛认可。TypeScript 发展至今,已经成为很多大型项目的标配,其提供的静态类型系统,大大增强了代码的可读性、可维护性和代码质量。同时,它提供最新的JavaScript特性,能让我们构建更加健壮的组件,新版本不断迭代更新,编写前端代码也越来越香。

-
类型有利于代码的重构,它有利于编译器在编译时而不是运行时发现错误; -
类型是出色的文档形式之一,良好的函数声明胜过冗长的代码注释,通过声明即可知道具体的实现;
-
免费开源,使用 Apache 授权协议; -
基于ECMAScript 标准进行拓展,是 JavaScript 的超集; -
添加了可选静态类型、类和模块; -
可以编译为可读的、符合ECMAScript 规范的 JavaScript; -
成为一款跨平台的工具,支持所有的浏览器、主机和操作系统; -
保证可以与 JavaScript 代码一起使用,无须修改(这一点保证了 JavaScript 项目可以向 TypeScript 平滑迁移); -
文件扩展名是 ts/tsx; -
编译时检查,不污染运行时;
2.1 安装 TypeScript 依赖环境
npm install --save-dev typescript ts-node
2.1.1 集成 Babel
npm install -D /preset-typescript// babel.config.js{"presets": [// ..."@babel/preset-typescript"]}
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
在 .eslintrc.js 配置文件中添加选项:
"parser": "@typescript-eslint/parser","plugins": ["@typescript-eslint"],// 可以直接启用推荐的规则"extends": ["eslint:recommended","plugin:@typescript-eslint/recommended"]// 也可以选择自定义规则"rules": {"@typescript-eslint/no-use-before-define": "error",// ...}
2.2 配置 TypeScript
-
strict: 是否启用严格类型检查选项,可选 ture | false -
allowUnreachableCode:是否允许不可达的代码出现,可选 ture | false -
allowUnusedLabels: 是否报告未使用的标签错误,可选 ture | false -
noImplicitAny: 当在表达式和声明上有隐式的 any 时是否报错,可选 ture | false -
strictNullChecks: 是否启用严格的 null 检查,可选 ture | false -
noImplicitThis: 当 this 表达式的值为 any 时,生成一个错误,可选 ture | false -
alwaysStrict: 是否以严格模式检查每个模块,并在每个文件里加入 use strict,可选 ture | false -
noImplicitReturns: 当函数有的分支没有返回值时是否会报错,可选 ture | false -
noFallthroughCasesInSwitch: 表示是否报告 switch 语句的 case 分支落空(fallthrough)错误;
-
moduleResolution: 模块解析策略默认为 node 比较通用的一种方式基 -
commonjs 模块标准,另一种是 classic 适用于其他 module 标准,如 amd、 umd、 esnext 等等 -
baseUrl: “./“ 用于解析非相对模块名称的根目录 -
paths: 模块名到基于 baseUrl 的路径映射的列表,格式 {} -
rootDirs: 根文件夹列表,其做好内容表示项目运行时的结果内容,格式 [] -
typeRoots: 包含类型声明的文件列表,格式 [“./types”] ,相对于配置文件的路径解析; -
allowSyntheticDefaultImports: 是否允许从没有设置默认导出的模块中默认导入
-
sourceRoot:./ 指定调试器应该找到 TypeScript 文件而不是源文件的位置 -
mapRoot: ./ 指定调试器应该找到映射文件而不是生成文件的位置 -
inlineSourceMap: 是否生成单个 sourceMap 文件,不是将 sourceMap 生成不同的文件 -
inlineSources: 是否将代码与 sourceMap 生成到一个文件中,要求同时设置 inlineSourceMap 和 sourceMap 属性
-
experimentalDecorators: 是否启用装饰器 -
emitDecoratorMetadata: 是否为装饰器提供元数据的支持
"exclude": ["node_modules","dist"...],

图中蓝色的为基本类型,红色为 TypeScript 支持的特殊类型
// let 或 const 变量名:数据类型 = 初始值;//例如:let varName: string = 'hello typescript'函数声明,推荐使用函数表达式,也可以使用箭头函数显得更简洁一下:
let 或 const 函数表达式名 = function(参数1:类型,参数2:类型):类型{// 执行代码// return xx;}// 例如let sum = function(num1: number, num2: number): number {return num1 + num2;}2.4 TypeScript 特殊类型介绍
2.4.1 any 任意值
2.4.2 void、null 和 undefined
-
空值 void 表示不返回任何值,一般用于函数定义返回类型时使用,用 void 关键字表示没有任何返回值的函数,void 类型的变量只能赋值为 null 和 undefined,不能赋值给其他类型上(除了 any 类型以外); -
null 表示不存在的对象值,一般只当作值来用,而不是当作类型使用; -
undefined 表示变量已经声明但是尚未初始化的变量的值,undefined 通常也是当作值来使用;
null 和 undefined 是所有类型的子类型,我们可以把 null 和 undefined 赋值给任何类型的变量。如果开启了 strictNullChecks 配置,那么 null 和 undefined 只能赋值给 void 和它们自身,这能避免很多常见的问题。
2.4.3 枚举
enum SendType {SEND_NORMAL,SEND_BATCH,SEND_FRESH,...}console.log(SendType.SEND_NORMAL === 0) // trueconsole.log(SendType.SEND_BATCH === 1) // trueconsole.log(SendType.SEND_FRESH === 2) // true
// 数值枚举enum SendType {SEND_NORMAL = 1,SEND_BATCH = 2,SEND_FRESH, // 按以上规则自动赋值为 3...}const sendtypeVal = SendType.SEND_BATCH;// 编译后输出代码var SendType;(function (SendType) {SendType[SendType["SEND_NORMAL"] = 1] = "SEND_NORMAL";SendType[SendType["SEND_BATCH"] = 2] = "SEND_BATCH";SendType[SendType["SEND_FRESH"] = 3] = "SEND_FRESH"; // 按以上规则自动赋值为 3})(SendType || (SendType = {}));var sendtypeVal = SendType.SEND_BATCH;// 字符串枚举enum PRODUCT_CODE {P1 = 'ed-m-0001', // 特惠送P2 = 'ed-m-0002', // 特快送P4 = 'ed-m-0003', // 同城即日P5 = 'ed-m-0006', // 特瞬送城际}
// 使用常量枚举编译前const enum SendType {SEND_NORMAL = 1,SEND_BATCH = 2,SEND_FRESH // 按以上规则自动赋值为 3}const sendtypeVal = SendType.SEND_BATCH;// 编译后var sendtypeVal = 2 /* SendType.SEND_BATCH */;
2.4.5 元组类型
// 元祖示例let row: [number, string, number] = [1, 'hello', 88];
interface CountDown {readonly uuid: string // 只读属性time: numberautoStart: booleanformat: stringvalue: string | number // 联合类型,支持字符串和数值型[key: string]: number // 字符串的键,数值型的值}interface CountDown {finish?: () => void // 可选类型millisecond?: boolean // 可选方法}// 接口可以重复声明,多次声明可以合并为一个接口
interface Style {color: string}interface: Shape {name: string}interface: Circle extends Style, Shape {radius: number// 还会包含继承的属性// color: string// name: string}const circle: Circle = { // 包含 3 个属性radius: 1,color: 'red',name: 'circle'}
2.4.7 类型别名 type
type StrOrNum = string | number// 用法和其它基本类型一样let sample: StrOrNumsample = 123sample = '123'sample = true // 错误
type Text = string | { text: string } // 联合类型type Coordinates = [number, number] // 元组类型type Callback = (data: string) => void // 函数类型type Shape = { name: string } // 对象类型type Circle = Shape & { radius: number} // 交叉类型,包含了 name 和 radius 属性
-
类型别名能够表示非对象类型,接口则只能表示对象类型,因此我们想要表示原始类型、联合类型和交叉类型时只能使用类型别名; -
类型别名不支持继承,接口可以继承其它接口、类等对象类型,类型别名可以借助交叉类型来实现继承的效果; -
接口名总是会显示在编译器的诊断信息和代码编辑器的智能提示信息中,而类型别名的名字只在特定情况下显示; -
接口具有声明合并的行为,而类型别名不会进行声明合并;
2.4.8 命名空间 namespace
namespace 命名空间名 {const 私有变量;export interface 接口名;export class 类名;}// 如果需要在命名空间外部调用需要添加 export 关键字命名空间名.接口名;命名空间名.类名;命名空间名.私有变量; // 错误,私有变量不允许访问
// 多文件命名空间// Validation.tsnamespace Validation {export interface StringValidator {isAcceptable(s: string): boolean;}}// NumberValidator.tsnamespace Validation { // 相同命名空间export interface NumberValidator {isAcceptable(num: number): boolean;}}
class Queue {private data = []push = item => this.data.push(item)pop = () => this.data.shift()}const queue = new Queue()// 在没有约束的情况下,开发人员很可能进入误区,导致运行时错误(或潜在问题)queue.push(0) // 最初是数值类型queue.push('1') // 有人添加了字符串类型// 使用过程中,走入了误区console.log(queue.pop().toPrecision(1));console.log(queue.pop().toPrecision(1)); // 运行时错误
class QueueOfNumber {private data: number[] = []push = (item: number) => this.data.push(item)pop = (): number => this.data.shift()}const queue = new Queue()queue.push(0)queue.push('1') // 错误,不能放入一个 字符串类型 的数据
class Queue<T> {private data: T[] = []push = (item: T) => this.data.push(item)pop = (): T | undefined => this.data.shift()}// 数值类型const queue = new Queue<number>()queue.push(0)queue.push(1)// 或者 字符串类型const queue = new Queue<string>()queue.push('0')queue.push('1')
2.4.10 类型断言
-
expr(<目标类型>值、对象或者表达式); -
expr as T (值或者对象 as 类型); -
expr as const 或 expr 可以将某类型强制转换成不可变类型; -
expr!(!类型断言):非空类型断言运算符 “!” 是 TypeScript 特有的类型运算符;
3.1 解耦关注点
type TransformFunction = (value: number) => numberfunction doNothing(value: number): number ( // doNothing() 只返回原数据,不进行任何处理return value)function getNumbers(transform: TransformFunction = doNothing): number[] {/** */}
type PluckFunction = (widgets: Widget) => Widget[]function pluckAll(widgets: Widget[]): Widget[] (// pluckAll() 返回全部,不进行任何处理return widgets)// 如果用户没有提供 pluck() 函数,则返回 pluckAll 作为实参的默认值function assembleWidgets(pluck: PluckFunction = pluckAll): AssembledWidget[] {/** */}
function identity<T>(value: T): T ( // 有一个类型参数 T 的泛型恒等函数return value)// 可以使用 identity 代替 doNothing 和 pluckAll
3.2 泛型数据结构
class NumberBinaryTreeNode {value: numberleft: NumberBinaryTreeNode | undefinedright: NumberBinaryTreeNode | undefinedconstructor(value: number) {this.value = value}}
class StringLinkedListNode {value: stringnext: StringLinkedListNode | undefinedconstructor(value: string) {this.value = value}}
class BinaryTreeNode<T> {value: Tleft: BinaryTreeNode<T> | undefinedright: BinaryTreeNode<T> | undefinedconstructor(value: T) {this.value = value}}
class LinkedListNode<T> {value: stringnext: LinkedListNode | undefinedconstructor(value: string) {this.value = value}}
type IteratorResult<T> = {done: booleanvalue: T}interface Iterator<T> {next(): IteratorResult<T>}interface IterableIterator<T> extends Iterator<T> {[Symbol.iterator](): IterableIterator<T>;}function* linkedListIterator<T>(head: LinkedListNode): IterableIterator<T> {let current: LinkedListNode<T> | undefined = headwhile (current) {yield current.value // 在遍历链表过程中,交出每个值current = current.next}}class LinkedListNode<T> implements Iterable<T> {value: Tnext: LinkedListNode<T> | undefinedconstructor(value: T) {this.value = value}// Symbol.iterator 是 TypeScript 特有语法,预示着当前对象可以使用 for ... of 遍历[Symbol.iterator](): Iterator<T> {return linkedListIterator(this)}}
4.1 常用注释指令
-
// @ts-nocheck: 为某个文件添加这个注释,就相当于告诉编译器不对该文件进行类型检查。即使存在错误,编译器也不会报错; -
// @ts-check: 与上个注释相反,可以在某个特定的文件添加这个注释指令,告诉编译器对该文件进行类型检查; -
// @ts-ignore: 注释指令的作用是忽略对某一行代码进行类型检查,编译器进行类型检查时会跳过指令相邻的下一行代码;
4.2 JSDoc 与类型
TypeScript 编译器可以自动推断出大部分代码的类型信息,也能从 JSDoc 中提取类型信息,以下是TypeScript 编译器支持的部分 JSDoc 标签:
-
@typedef 标签能够创建自定义类型; -
@type 标签能够定义变量类型; -
@param 标签用于定义函数参数类型; -
@return 和 @returns 标签作用相同,都用于定义函数返回值类型; -
@extends 标签定义继承的基类; -
@public @protected @private 标签分别定义类的公共成员、受保护成员和私有成员; -
@readonly 标签定义只读成员;
4.3 三斜线指令
-
Partial:构造一个新类型,并将类型 T 的所有属性变为可选属性; -
Required:构造一个新类型,并将类型 T 的所有属性变为必选属性; -
Readonly: 构造一个新类型,并将类型 T 的所有属性变为只读属性; -
Pick: 已有对象类型中选取给定的属性名,返回一个新的对象类型; -
Omit: 从已有对象类型中剔除给定的属性名,返回一个新的对象类型;
interface A {x: numbery: numberz?: string}type T0 = Partial<A>// 等价于type T0 = {x?: number | undefined;y?: number | undefined;z?: string | undefined;}type T1 = Required<A>// 等价于type T1 = {x: number;y: number;z: string;}type T2 = Readonly<A>// 等价于type T2 = {readonly x: number;readonly y: number;readonly z?: string | undefined;}type T3 = Pick<A, 'x'>// 等价于type T3 = {x: number;}type T4 = Omit<A, 'x'>// 等价于type T4 = {y: number;z?: string | undefined;}
6.1 TypeScript 演练场
-
左侧编写 TS 代码,右侧自动生成编译后的代码; -
可以自主选择 TypeScript 编译版本; -
版本列表最后一项是一个特殊版本 “Nightly” 即 “每日构建版本”,想尝试最新功能可以试试; -
支持 TypeScript 大部分配置项和编译选项,可以模拟本地环境,查看代码片段的输出结果;
6.2 JSDoc Generator 插件
选择 Generate JSDoc 为当前光标处代码生成文档注释;
选择Generate JSDoc for the current file 为当前文件生成文档注释;
6.4 模块导入自动归类和排序
6.5 启用 CodeLens
显示函数、类、方法和接口等被引用的次数以及被哪些代码引用;
显示接口被实现的次数以及谁实现了该接口;
6.6 接口自动生成 TypeScript 类型
{"a":1,"b":"2","c":3} // 复制这段 JSON 代码// Generated by https://quicktype.ioexport interface Obj {a: number;b: string;c: number;}

