1. What is TypeScript?
TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。
其次引用官网的定义:
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source.
翻译成中文即是:
TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。
2. Why TypeScript?
TypeScript 官网列举了一些优势,大致也可分为以下几点:
2.1 TypeScript 增加了代码的可读性和可维护性
- 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
- 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、代码重构等
2.2 TypeScript 非常包容
TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
- 即使不显式的定义类型,也能够自动做出类型推论
- TypeScript 的类型系统是图灵完备的,可以定义从简单到复杂的几乎一切类型
- 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取
2.3 TypeScript 拥有活跃的社区
大部分第三方库都有提供给 TypeScript 的类型定义文件
- Angular、Vue、VS Code、Ant Design 等等耳熟能详的项目都是使用 TypeScript 编写的
TypeScript 拥抱了 ES6 规范,支持 ESNext 草案中处于第三阶状态(Stage 3)的特性
2.4 TypeScript 的缺点
任何事物都是有两面性的,TypeScript也不例外 :
有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念
- 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本
- 集成到构建流程需要一些工作量
- 可能和一些库结合的不是很完美(需要库提供.d.ts声明文件或者需要自行定义)
2.5 与JavaScript的对比
| 特性 | TypeScript | JavaScript | | —- | —- | —- | | 明确类型要求 | √ | × | | 类的概念 | √ | × | | 静态类型检查 | √ | × | | 函数缺省参数值 | √ | × | | 模块的概念 | √ | × |
3. How to use TypeScript?
安装 TypeScript
TypeScript 的命令行工具安装方法如下:
npm install -g typescript
以上命令会在全局环境下安装 tsc 命令,安装完成之后,我们就可以在任何地方执行 tsc 命令了。
编译一个 TypeScript 文件很简单:
tsc hello.ts
在webpack集成项目中使用TypeScript
// 生成tsconfig.json文件tsc --init// 安装ts-loader依赖npm i ts-loader -D// 添加ts-loaderrules: [{// ts文件test: /\.ts$/,loader: 'ts-loader',options: { appendTsSuffixTo: [/\.vue$/] } // 若你的项目是以vue来开发,也需要对.vue文件进行识别}]
4. TypeScript基础概念
原始数据类型
原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol 和 BigInt。// 布尔值是最基础的数据类型,在 TypeScript 中,使用 boolean 定义布尔值类型let isDone: boolean = false;// 实质上new Boolean() 返回的是一个 Boolean 对象:let createdByNewBoolean: boolean = new Boolean(1);// Type 'Boolean' is not assignable to type 'boolean'.// 'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.// 直接调用 Boolean 也可以返回一个 boolean 类型let createdByBoolean: boolean = Boolean(1);在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样// 数值类型let decLiteral: number = 6;let hexLiteral: number = 0xf00d;// ES6 中的二进制表示法let binaryLiteral: number = 0b1010;// ES6 中的八进制表示法let octalLiteral: number = 0o744;let notANumber: number = NaN;let infinityNumber: number = Infinity;// 字符串类型let myName: string = 'Tom';// 空值类型// JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:function alertName(): void {alert('My name is Tom');}声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:let unusable: void = undefined;// Null 和 Undefined在 TypeScript 中,可以使用 null 和 undefined 来定义这两个原始数据类型:let u: undefined = undefined;let n: null = null;// 值得注意的是!!两者和void的区别在于undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量。而void不行
任意值
// 任意值(Any)用来表示允许赋值为任意类型//变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:let something; ==> let something: any;
类型推论
// 如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型let myFavoriteNumber = 'seven'; ==> let myFavoriteNumber: string = 'seven';// 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
联合类型
// 联合类型(Union Types)表示取值可以为多种类型中的一种。let myFavoriteNumber: string | number;myFavoriteNumber = 'seven';myFavoriteNumber = 7;// 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法function getLength(something: string | number): number {return something.length;}// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.// Property 'length' does not exist on type 'number'.// 此时的情况可以使用类型保护来进行解决(1.typeof 2.instanceof 3.in 4.字面量类型保护 5.使用定义的类型保护)
对象的类型——接口 ``` // 在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement) interface Person { name: string; age: number; } let tom: Person = { name: ‘Tom’, age: 25 }; // 有时我们希望不要完全匹配一个形状,那么可以用可选属性: interface Person { name: string; age?: number; } let tom: Person = { name: ‘Tom’ }; // 有时候我们希望一个接口允许有任意的属性,可以使用如下方式 // 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集 interface Person { name: string; age?: number; [propName: string]: string | number; } let tom: Person = { name: ‘Tom’, gender: ‘male’ }; // 有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性: interface Person { readonly id: number; name: string; age?: number;
[propName: string]: any;
} let tom: Person = { id: 89757, name: ‘Tom’, gender: ‘male’ }; tom.id = 9527; // index.ts(14,5): error TS2540: Cannot assign to ‘id’ because it is a constant or a read-only property.
- 数组的类型
// 最简单的方法是使用「类型 + 方括号」来表示数组:
let fibonacci: number[] = [1, 1, 2, 3, 5];
// 数组的项中不允许出现其他的类型:
let fibonacci: number[] = [1, ‘1’, 2, 3, 5];
// Type ‘string’ is not assignable to type ‘number’.
// 数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push(‘8’);
// Argument of type ‘“8”‘ is not assignable to parameter of type ‘number’.
// 我们也可以使用数组泛型(Array Generic) Array
[index: number]: number;
} let fibonacci: NumberArray = [1, 1, 2, 3, 5]; // 类数组(Array-like Object)不是数组类型,比如 arguments: function sum() { let args: number[] = arguments; } // Type ‘IArguments’ is missing the following properties from type ‘number[]’: pop, push, concat, join, and 24 more. // 事实上常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等
- 函数的类型
// 一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单: function sum(x: number, y: number): number { return x + y; } // 函数表达式 let mySum: (x: number, y: number) => number = function (x: number, y: number): number { return x + y; }; // 注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。 // 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。在 ES6 中,=> 叫做箭头函数 // 与接口中的可选属性类似,我们用 ? 表示可选的参数: function buildName(firstName: string, lastName?: string) { if (lastName) { return firstName + ‘ ‘ + lastName; } else { return firstName; } } let tomcat = buildName(‘Tom’, ‘Cat’); let tom = buildName(‘Tom’); // 需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了 // 在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数: function buildName(firstName: string, lastName: string = ‘Cat’) { return firstName + ‘ ‘ + lastName; } let tomcat = buildName(‘Tom’, ‘Cat’); let tom = buildName(‘Tom’); // 此时就不受「可选参数必须接在必需参数后面」的限制了 // 可以使用 …rest 的方式获取函数中的剩余参数(rest 参数): function push(array, …items) { items.forEach(function(item) { array.push(item); }); } let a: any[] = []; push(a, 1, 2, 3); // 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。 function reverse(x: number): number; function reverse(x: string): string; function reverse(x: number | string): number | string { if (typeof x === ‘number’) { return Number(x.toString().split(‘’).reverse().join(‘’)); } else if (typeof x === ‘string’) { return x.split(‘’).reverse().join(‘’); } }
- 类型断言
// 类型断言(Type Assertion)可以用来手动指定一个值的类型。 // 值 as 类型 或 <类型>值
- 声明文件当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。语法:- declare var 声明全局变量<br />- declare function 声明全局方法<br />- declare class 声明全局类<br />- declare enum 声明全局枚举类型<br />- declare namespace 声明(含有子属性的)全局对象<br />- interface 和 type 声明全局类型<br />- export 导出变量<br />- export namespace 导出(含有子属性的)对象<br />- export default ES6 默认导出<br />- export = commonjs 导出模块<br />- export as namespace UMD 库声明全局变量<br />- declare global 扩展全局变量<br />- declare module 扩展模块<br />- /// <reference /> 三斜线指令<br />- 内置对象
// ECMAScript 标准提供的内置对象有:Boolean、Error、Date、RegExp 等。 // 我们可以在 TypeScript 中将变量定义为这些类型: let b: Boolean = new Boolean(1); let e: Error = new Error(‘Error occurred’); let d: Date = new Date(); let r: RegExp = /[a-z]/; // DOM 和 BOM 提供的内置对象有:Document、HTMLElement、Event、NodeList 等。 let body: HTMLElement = document.body; let allDiv: NodeList = document.querySelectorAll(‘div’); document.addEventListener(‘click’, function(e: MouseEvent) { // Do something }); ```
