git book - TypeScript入门教程
学习地址个人学习笔记,以下内容与原内容有微小区别;
-
1.简介
- 什么是TypeScript?
- 定义: TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。
- 优点: 1.增加了代码可维护性;2.包容,可兼容JS代码;3.活跃的社区;
- 缺点: 1.增加集成到构建的成本:例如编译代码过慢、部署流程更加复杂;2.与部分JS库兼容问题;3.学习成本;
- 安装TypeScript
- 安装指令:
- npm install -g typescript
- 编译指令:
- tsc hello.ts
- 推荐编辑器: Visual Studio Code 该编辑器也是用typeScript编写!
- Hello TypeScript
- 第一个简单的Demo
- TypeScript 只会进行静态检查,如果发现有错误,编译的时候就会报错。
- TypeScript 编译的时候即使报错了,还是会生成编译结果
-
2.基础
- 原始数据类型
- JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
- 原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。
- let isDone: boolean = false;
- let decLiteral: number = 6;
- let myName: string = 'wteamxq';
- function alertName(): void { alert('My name is Tom'); }
- let u: undefined = undefined;
- let n: null = null;
- 任意值
- 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型;
- let something; something = 'seven'; something = 7;
- 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
- let anyThing: any = 'hello'; anyThing.myName.setFirstName('Cat');
- 类型推论
- 变量声明时赋了某一类型值,则默认变量为该类型, 后续更改会报错;
- let myFavoriteNumber = 'seven';myFavoriteNumber = 7;
- // index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
- 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
- let myFavoriteNumber; myFavoriteNumber = 7; // 不会报错
- 联合类型
- 联合类型(Union Types)表示取值可以为多种类型中的一种。
- let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; myFavoriteNumber = 7;
- 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
- function getLength(something: string | number): number { return something.length; }
- // 报错, length方法只属于 string, number 类型没该方法;
- 对象的类型 - 接口
- 在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
- 在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
- TypeScript 中的接口理解成一个形状:「对象的形状(Shape)」
- interface Person { name: string; age: number; } // 注意字段间分隔符是分号';', 而不是逗号','
- let User: Person = {name: 'wteamxq', age: 25, };
- 可选属性: 有时我们希望不要完全匹配一个形状,那么可以用可选属性;
- interface Person { name: string; age?:number; }
- 任意属性:有时候我们希望一个接口允许有任意的属性;
- interface Psersion { name: string; [propName: string]: any; } // 类型为string的任意属性名
- 只读属性:有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性;
- interface Psersion { readonly id: number; name: string; }
- 数组的类型
- 在 TypeScript 中,数组类型有多种定义方式,比较灵活。
- 「类型 + 方括号」表示法
- let fibonacci: number[] = [1, 1, 2, 3, 5];// 定义只有数字的数组
- 数组泛型(Array Generic): Array
来表示数组 - let fibonacci: Array
= [1, 1, 2, 3, 5]; // 更多范型细节: 在 进阶 - 泛型 - 用接口表示数组
- interface NumberArray { [index: number]: number; }
- let fibonacci: NumberArray = [1, 2, 3, 5];
- any 在数组中的应用
- let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
- 类数组(Array-like Object): 不是数组类型,比如 arguments:
- function sum() { let args: number[] = arguments; }
- // 报错,类数组不是数组;
- // index.ts(2,7): error TS2322: Type 'IArguments' is not assignable to type 'number[]'.
- 函数的类型
- 函数是 JavaScript中的一等公民
- 在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression):
- 函数声明
- 对函数的输入、输出进行约束:
- 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 中,=> 叫做箭头函数,应用十分广泛,可以参考 ES6 中的箭头函数。
- 用接口定义函数的形状
- interface SearchFunc { (source: string, subString: string): boolean; }
- let mySearch: SearchFunc; mySearch = function(source: string, subString:string) { return source.search(subString) !== -1; }
- 可选参数
- function buildName(firstName: string, lastName?:string) { if (lastName) { return firstName + lastName;} else { return firstName; } }
- let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
- 可选参数后面不允许再出现必须参数了;
- 参数默认值
- function buildName(firstName: string = 'Tom', lastName: string) { return firstName + lastName; }
- TypeScript 会将添加了默认值的参数识别为可选参数, 不受「可选参数必须接在必需参数后面」的限制;
- 剩余参数
- ES6 中,可以使用 ...rest 的方式获取函数中的剩余参数(rest 参数);
- 事实上,items 是一个数组。所以我们可以用数组的类型来定义它:
- function push(array: any[], ...items: any[]) { items.forEach(function (item) { array.push(item);} ); }
- 重载
- 重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
- 比如,我们需要实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
- 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(''); } }
- TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
- 类型断言
- 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
- 而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型的属性或方法;
- 类型断言 就能解决以上问题;
- function getLength(something: string | number): number { if ((<string>something).length) { return (<string>something).length; } else { return something.toString().length; } }
- 类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的;
- 声明文件
- 当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
- 声明语句:将第三方库例如JQ声明为全局变量防止编译报错;
- 声明文件:放置声明语句的单独文件,*.d.ts 格式,必须.d.ts后缀;
- 书写声明文件;
- 6场景:1.全局变量;2.npm包;3.UMD库;4.直接扩展全局变量;5.在npm包或UMD库改变全局变量;6.模块插件;
- 全局变量:npm install @types/xxx --save-dev 安装类不需要配置;
- npm包:一般来说,npm 包的声明文件可能存在于两个地方:1.与该 npm 包绑定在一起。2.发布到 @types 里。
- UMD库:既可以通过 <script> 标签引入,又可以通过 import 导入的库,称为 UMD 库。
- 直接扩展全局变量: 有的第三方库扩展了一个全局变量,可是此全局变量的类型却没有相应的更新过来,就会导致 ts 编译错误,此时就需要扩展全局变量的类型。
- 在 npm 包或 UMD 库中扩展全局变量: 一个 npm 包或者 UMD 库的声明文件,只有 export 导出的类型声明才能被导入。所以对于 npm 包或 UMD 库,如果导入此库之后会扩展全局变量,则需要使用另一种语法在声明文件中扩展全局变量的类型,那就是 declare global。
- 自动生成声明文件:如果库的源码本身就是由 ts 写的,那么在使用 tsc 脚本将 ts 编译为 js 的时候,添加 declaration 选项,就可以同时也生成 .d.ts 声明文件了。
- 发布声明文件:两种方案:1.将声明文件和源码放在一起;2.将声明文件发布到 @types 下;
- 内置对象
- JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
- ECMAScript 的内置对象: Boolean、Error、Date、RegExp等
- DOM 和 BOM 的内置对象: Document、HTMLElement、Event、NodeList 等。
- 用 TypeScript 写 Node.js Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:
- npm install @types/node --save-dev
-
3.进阶
- 类型别名
- 类型别名用来给一个类型起个新名字。
- 字符串字面量类型
- 字符串字面量类型用来约束取值只能是某几个字符串中的一个。
- type EventNames = 'click' | 'scroll' | 'mousemove';
- function handleEvent(ele: Element, event: EventNames) { // do something }
- handleEvent(document.getElementById('hello'), 'scroll'); // 没问题
- handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'
- 元组
- 数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。
- let xcatliu: [string, number] = ['Xcat Liu', 25];
- 枚举
- 枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
- enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
- 手动赋值
- enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
- 枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。
- 上述例子就是 常数项;
- 计算所得项: enum Color {Red, Green, Blue = "blue".length};
- 常数枚举是使用 const enum 定义的枚举类型:
- 外部枚举(Ambient Enums)是使用 declare enum 定义的枚举类型:
- 类
- 面向对象(OOP)的三大特性:封装、继承、多态
- 类的概念: 封装、继承、多态、存取器、修饰符、抽象类、接口;
- ES6 中类的用法,更详细的介绍可以参考 ECMAScript 6 入门 - Class
- 属性和方法
- 类的继承
- 存取器
- 静态方法
- ES7 中类的用法
- ES6 中实例的属性只能通过构造函数中的 this.xxx 来定义,ES7 提案中可以直接在类里面定义:
- ES7 提案中,可以使用 static 定义一个静态属性:
- TypeScript 中类的用法
- TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected。
- 抽象类: 抽象类是不允许被实例化的;抽象类中的抽象方法必须被子类实现;
- 类和接口
- 类实现接口: 实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。
- interface Alarm { alert(); }
- class Door { }
- class SecurityDoor extends Door implements Alarm { alert() { console.log('SecurityDoor alert'); } }
- class Car implements Alarm { alert() { console.log('Car alert'); } }
- 一个类可以实现多个接口:
- class Car implements Alarm, Light {}
- 接口与接口之间可以是继承关系:
- interface Alarm { alert(); }
- interface LightableAlarm extends Alarm { lightOn(); lightOff(); }
- 接口也可以继承类:
- class Point{} interface Point3d extends Point{}
- 泛型
- 泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
- 例如:实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
- function createArray(length: number, value: any): Array
{} - 这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型: Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型。 这时候,泛型就派上用场了:
- function createArray
(length: number, value: T): Array {} - 定义泛型的时候,可以一次定义多个类型参数:
- 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
- 通过接口,可以对泛型进行约束;
- 泛型接口:使用含有泛型的接口来定义函数的形状。
- 泛型类:泛型也可以用于类的类型定义中。
- 声明合并
- 如果定义了两个相同名字的函数、接口或类,那么它们会合并成一个类型:
- 函数的合并:可以使用重载定义多个函数类型;具体参考: 基础 -> 函数类型 -> 重载;
- 接口的合并:合并的属性的类型必须是唯一的;
- 类的合并: 类的合并与接口的合并规则一致。
-
4.工程
- 代码检查
- TSLint 与 ESLint 作为检查 TypeScript 代码的工具,各自有各自的优点:
- TSLint 的优点: 1.专为 TypeScript 服务,bug 比 ESLint 少; 2.不受限于 ESLint 使用的语法树 ESTree;3.能直接通过 tsconfig.json 中的配置编译整个项目,使得在一个文件中的类型定义能够联动到其他文件中的代码检查;
- ESLint 的优点: 1.基础规则比 TSLint 多很多(249 : 151); 社区繁荣,插件众多(50+ : 9);