参考文档:
https://ts.xcatliu.com/introduction/get-typescript.html


一、 为什么选择ts —- (试图弥补 js 是弱类型语言的缺点)

TypeScript 增加了代码的可读性和可维护性

  • 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了
  • 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
  • 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等

TypeScript 非常包容

  • TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
  • 即使不显式的定义类型,也能够自动做出类型推论。(可以根据代码推测出变量是什么类型)
  • 可以定义从简单到复杂的几乎一切类型
  • 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
  • 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取

TypeScript 拥有活跃的社区

  • 由google和microsoft编写
  • TypeScript 拥抱了 ES6 规范,也支持部分 ESNext 草案的规范

TypeScript 的缺点

  • 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念
  • 可能和一些库结合的不是很完美

接口(Interfaces):
泛型(Generics):

类(Classes):

枚举类型(Enums):
**


二、 安装ts


TypeScript 的命令行工具安装方法如下:

  1. npm install -g typescript

以上命令会在全局安装 tsc 命令,运用tsc命令可以直接编译一个 TypeScript 文件.

  1. tsc hello.ts

约定使用TypeScript编写的文件以 .ts 为后缀,用 TypeScript 编写React时,以 .tsx 为后缀。

注 :

> TypeScript 中,使用 : 指定变量的类型,: 的前后有没有空格都可以。
> TypeScript 编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件。
> 如果要在报错的时候终止 js 文件的生成,可以在 tsconfig.json 中配置 noEmitOnError 即可。(可参考)—- 配置后好像**没什么用**

三、 数据类型

JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types).
原始数据类型包括:布尔值、数值、字符串、nullundefined 以及 ES6 中的新类型 Symbol

布尔值:

  1. let isDone: boolean = false; // 编译通过
  2. let createdByNewBoolean: boolean = new Boolean(1); // 编译失败
  3. // Type 'Boolean' is not assignable to type 'boolean'.

new Boolean() 返回的是一个 Boolean 对象:

  1. let createdByNewBoolean: Boolean = new Boolean(1); // 编译通过

注 :

> 在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 nullundefined)一样,

数值:

  1. let decLiteral: number = 6;
  2. let hexLiteral: number = 0xf00d;
  3. // ES6 中的二进制表示法
  4. let binaryLiteral: number = 0b1010;
  5. // ES6 中的八进制表示法
  6. let octalLiteral: number = 0o744;
  7. let notANumber: number = NaN;
  8. let infinityNumber: number = Infinity;

转义结果:

  1. var decLiteral = 6;
  2. var hexLiteral = 0xf00d;
  3. // ES6 中的二进制表示法
  4. var binaryLiteral = 10;
  5. // ES6 中的八进制表示法
  6. var octalLiteral = 484;
  7. var notANumber = NaN;
  8. var infinityNumber = Infinity;

其中 0b10100o744ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。

ES6 提供了二进制和八进制数值的新的写法,分别用前缀0b(或0B)和0o(或0O)表示。

ES6 在Number对象上,新提供了Number.isFinite()Number.isNaN()两个方法。

ES6 将全局方法parseInt()parseFloat(),移植到Number对象上面,行为完全保持不变
Number.isInteger()用来判断一个数值是否为整数。 ……

字符串:

  1. let myName: string = 'Tom';
  2. let myAge: number = 25;
  3. // 模板字符串
  4. let sentence: string = `Hello, my name is ${myName}.
  5. I'll be ${myAge + 1} years old next month.`;

转义结果:

  1. var myName = 'Tom';
  2. var myAge = 25;
  3. // 模板字符串
  4. var sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month.";

空值:

JavaScript中没有(Void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:

  1. function alertName(): void {
  2. alert('My name is Tom');
  3. }
  4. // 声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
  5. let unusable: void = undefined;

undefined && null:

在 TypeScript 中,可以使用 nullundefined 来定义这两个原始数据类型:

  1. let u: undefined = undefined;
  2. let n: null = null
  3. // 这样会报错
  4. let num: number = undefined;
  5. // Type 'undefined' is not assignable to type 'number'.ts(2322)

四、 任意值(any)

任意值(Any)用来表示允许赋值为任意类型。

如果是一个普通类型,在赋值过程中改变类型是不被允许的。

  1. let myFavoriteNumber: string = 'seven';
  2. myFavoriteNumber = 7;
  3. // Type '7' is not assignable to type 'string'.ts(2322)
  4. let myFavoriteNumber1: any = 'seven';
  5. myFavoriteNumber1 = 7;
  6. // 不会报错
  1. let anyThing: string = 'hello';
  2. console.log(anyThing.myName);
  3. console.log(anyThing.myName.firstName);
  4. // Property 'myName' does not exist on type 'string'.
  5. let anyThing: any = 'Tom';
  6. anyThing.setName('Jerry');
  7. anyThing.setName('Jerry').sayHello();
  8. anyThing.myName.setFirstName('Cat');
  9. // 不会报错

注 :

> 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
> 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。

五、 类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个 类型


  1. let myFavoriteNumber = 'seven';
  2. myFavoriteNumber = 7;
  3. // Type '7' is not assignable to type 'string'.ts(2322)

六、 联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种

  1. let myFavoriteNumber: string | number;
  2. myFavoriteNumber = 'seven';
  3. myFavoriteNumber = 7;
  4. myFavoriteNumber = true; // 这行会报错
  5. // Type 'true' is not assignable to type 'string | number'.ts(2322)

访问联合类型的属性或方法

如果一个变量定义为 联合类型,ts是不知道该变量到底是属于什么类型;因此,ts 定义这种情况下,该变量可以访问 联合类型 中所有类型 共有的属性和方法。(访问了不是共有的,会抛错)


  1. function getLength(something: string | number): number {
  2. return something.length;
  3. }
  4. // Property 'length' does not exist on type 'string | number'.
  5. // Property 'length' does not exist on type 'number'.ts(2339)
  6. function getString(something: string | number): string {
  7. return something.toString();
  8. }
  9. // 调用toString方法是可以的,因为 string | number 都有这个方法

七、 对象的类型—接口

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述


  1. interface Person {
  2. name: string;
  3. age: number;
  4. }
  5. let tom: Person = {
  6. name: 'Tom',
  7. age: '25'
  8. };
  9. // Type 'string' is not assignable to type 'number'.ts(2322)
  10. // The expected type comes from property 'age' which is declared here on type 'Person'
  11. interface Person {
  12. name: string;
  13. age: number;
  14. }
  15. let tom: Person = {
  16. name: 'Tom'
  17. };
  18. // Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.ts(2741)
  • 我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致.
  • 接口一版首字母大写
  • 定义的变量比接口少了一些属性 也是不允许的, 多一些也不行(**赋值的时候,变量的形状必须和接口的形状保持一致)**

1. 可选属性

有时我们希望不要完全匹配一个形状,那么可以用可选属性

  1. interface Person {
  2. name: string;
  3. age?: number;
  4. }
  5. let tom: Person = {
  6. name: 'Tom'
  7. };
  • 选属性的含义是该属性可以不存在。
  • 仍然不允许添加未定义的属性
  1. interface Person {
  2. name: string;
  3. age?: number;
  4. }
  5. let tom: Person = {
  6. name: 'Tom',
  7. age: 25,
  8. gender: 'male'
  9. };
  10. // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
  11. // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.ts(2322)

2. 任意属性

有时候我们希望一个接口允许有任意的属性


  1. interface Person {
  2. name: string;
  3. age?: number;
  4. [propName: string]: any; // 此处的 string 限定key值,只能是 number / string
  5. }
  6. let tom: Person = {
  7. name: 'Tom',
  8. gender: 'male'
  9. };
  • 使用 [propName: string] 定义了任意属性取 string 类型的值。
  • 一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
  1. interface Person {
  2. name: string;
  3. age?: number; // 但是已确定的属性中 age可以为 number | undefined,且不是string的子属性
  4. [propName: string]: string; // 这里限制 任意属性 的类型,
  5. }
  6. let tom: Person = {
  7. name: 'Tom',
  8. age: 25,
  9. gender: 'male'
  10. };
  11. // Property 'age' of type 'number | undefined' is not assignable to string index type 'string'.ts(2411)
  12. // (property) Person.age?: number | undefined
  13. // Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
  14. // Property 'age' is incompatible with index signature.
  15. // Type 'number' is not assignable to type 'string'.ts(2322)

3. 只读属性

有时我们只希望对象中的一些字段 只能在创建的时候被赋值,可以使用readonly 定义只读属性

  1. interface Person {
  2. readonly id: number;
  3. name: string;
  4. age?: number;
  5. [propName: string]: any;
  6. }
  7. let tom: Person = {
  8. id: 89757,
  9. name: 'Tom',
  10. gender: 'male'
  11. };
  12. tom.id = 9527;
  13. // Cannot assign to 'id' because it is a read-only property.ts(2540)

八、 数组的类型定义方式

1. 类型 + [] 表示法

  1. let fibonacci: number[] = [1, 1, 2, 3, 5];
  2. // 表示数组中每一项 只能为 number
  3. let fibonacci: number[] = [1, '1', 2, 3, 5];
  4. // Type 'string' is not assignable to type 'number'.ts(2322)
  5. let fibonacci: number[] = [1, 1, 2, 3, 5];
  6. fibonacci.push('8');
  7. // Argument of type '"8"' is not assignable to parameter of type 'number'.ts(2345)
  8. let list: any[] = ['Xcat Liu', 25, { website: 'http://xcatliu.com' }];
  9. // any 在数组中的应用

2. 使用数组 泛型(Array Generic) Array 来表示数组:

  1. let fibonacci: Array<number> = [1, 1, 2, 3, 5];

3. 用接口表示数组

  1. interface NumberArray {
  2. [index: number]: number;
  3. }
  4. let fibonacci: NumberArray = [1,2,3,4,5]

NumberArray 表示:只要 index 的类型是 number,那么值的类型必须是 number

4. 类数组

类似数组,但不是数组,如 arguments和dom元素集合(通过 document.querySelector()获取的)

  1. function sum() {
  2. let args: number[] = arguments;
  3. }
  4. // Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 15 more.ts(2740)
  5. function sum() {
  6. let args: IArguments = arguments;
  7. }
  8. // 事实上常见的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等

九、 函数的类型

函数的声明

在 JavaScript 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式

  1. // 函数声明(Function Declaration)
  2. function sum(x, y) {
  3. return x + y;
  4. }
  5. // 函数表达式(Function Expression)
  6. let mySum = function (x, y) {
  7. return x + y;
  8. };

函数的约束

  1. // TypeScript对声明式函数的约束方式
  2. function sum(x: number, y: number): number {
  3. return x + y;
  4. }
  5. // TypeScript对函数表达式(Function Expression)的约束方式
  6. let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
  7. return x + y;
  8. };
  • 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型

用接口形式定义函数的参数

  1. interface SearchFunc {
  2. (source: string, subString: string): boolean; // 前面定义的是 函数的输入;后面boolean是输出类型
  3. }
  4. let mySearch: SearchFunc;
  5. mySearch = function(source: string, subString: string) {
  6. return source.search(subString) !== -1;
  7. }

函数的可选参数

  1. function buildName(firstName: string, lastName?: string) {
  2. if (lastName) {
  3. return firstName + ' ' + lastName;
  4. } else {
  5. return firstName;
  6. }
  7. }
  8. let tomcat = buildName('Tom', 'Cat');
  9. let tom = buildName('Tom');
  • 需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必须参数了

函数参数的默认值

TypeScript 会将添加了默认值的参数识别为可选参数 (那么添加默认值的参数也必须放在必须参数后面)

  1. function buildName(firstName: string, lastName: string = 'Cat') {
  2. return firstName + ' ' + lastName;
  3. }
  4. let tomcat = buildName('Tom', 'Cat'); // 输出 Tom Cat
  5. let tom = buildName('Tom'); // 输出 Tom Cat

剩余参数

  1. function push(array: any[], ...items: any[]) {
  2. items.forEach(function(item) {
  3. array.push(item);
  4. });
  5. }
  6. let a = [];
  7. push(a, 1, 2, 3);
  • 注意,rest 参数只能是最后一个参数

重载(重载就是一组具有相同名字、不同参数列表的函数(方法))

重载: 在同一个作用域中,如果有多个函数的名字相同,但是形参列表不同(参数类型不同或参数个数不同),返回值类型可同也可不同,我们称之为重载函数
高程3 p66页 标明 如果在ECMAScript 中定义了两个名字相同的函数,则改名字只属于后定义的函数。即js没有重载

重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。

  1. function reverse(x: number): number;
  2. function reverse(x: string): string;
  3. function reverse(x: number | string): number | string {
  4. if (typeof x === 'number') {
  5. return Number(x.toString().split('').reverse().join(''));
  6. } else if (typeof x === 'string') {
  7. return x.split('').reverse().join('');
  8. }
  9. }
  • TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面

十、 类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型

<类型>值

值 as 类型

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须用后一种

  1. function getLength(something: string | number): number {
  2. if ((<string>something).length) { // 这里不用类型断言的话,ts会报错
  3. return (<string>something).length;
  4. } else {
  5. return something.toString().length;
  6. }
  7. }
  • 类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的
  1. function toBoolean(something: string | number): boolean {
  2. return <boolean>something;
  3. }
  4. // Conversion of type 'string | number' to type 'boolean' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  5. Type 'number' is not comparable to type 'boolean'.ts(2352)

十一、 声明文件

声明文件必需以 .d.ts 为后缀

定义全局变量:

declear 声明全局变量仅仅用来定义类型,而不是具体的值。它仅仅会用于编译时的检查,在编译结果中会被删除.

  1. declare var jQuery: (selector: string) => any; // 以变量的形式定义
  2. npm install @types/jquery --save-dev; // 直接下载对应的包(需要发布包的作者加入了声明文件)
  3. declare function jQuery(selector: string): any; // 以函数的形式定义
  4. // ts中支持重载的方式 定义
  5. declare function jQuery(selector: string): any;
  6. declare function jQuery(domReadyCallback: () => any): any;
  • declare var 声明全局变量 // 基本数据类型
  • declare function 声明全局方法 // 全局变量为函数
  • declare class 声明全局类 // 全局变量为一个类时
  • declare enum 声明全局枚举类型 // 枚举类型
  • declare namespace 声明全局对象(含有子属性)// 拥有深层级,需要用嵌套的 namespace(命名空间)
  • interfacetype 声明全局类型 // 接口类型

namespace 中也可以嵌套 namespace

**

防止命名冲突

typeinterface 类似
暴露在最外层的 interfacetype 会作为全局类型作用于整个项目中,我们应该尽可能的减少全局变量或全局类型的数量。故应该将他们放到 namespace 下:

  1. declare namespace jQuery {
  2. interface AjaxSettings {
  3. method?: 'GET' | 'POST'
  4. data?: any;
  5. }
  6. function ajax(url: string, settings?: AjaxSettings): void;
  7. }

声明合并

假如 jQuery 既是一个函数,可以直接被调用 jQuery('#foo'),又是一个对象,拥有子属性 jQuery.ajax()(事实确实如此),则我们可以组合多个声明语句,它们会不冲突的合并起来:


  1. declare function jQuery(selector: string): any;
  2. declare namespace jQuery {
  3. function ajax(url: string, settings?: any): void;
  4. }
  5. jQuery('#foo');
  6. jQuery.ajax('/api/get_something');

export

  1. export const name: string;
  2. export function getName(): string;
  3. export class Animal {
  4. constructor(name: string);
  5. sayHi(): string;
  6. }
  7. export enum Directions {
  8. Up,
  9. Down,
  10. Left,
  11. Right
  12. }
  13. export interface Options {
  14. data: any;
  15. }

混用declare 和 export

  1. declare const name: string;
  2. declare function getName(): string;
  3. declare class Animal {
  4. constructor(name: string);
  5. sayHi(): string;
  6. }
  7. declare enum Directions {
  8. Up,
  9. Down,
  10. Left,
  11. Right
  12. }
  13. interface Options {
  14. data: any;
  15. }
  16. export namespace foo {
  17. const name: string;
  18. namespace bar {
  19. function baz(): string;
  20. }
  21. }
  22. export default function foo(): string;
  23. export {
  24. name,
  25. getName,
  26. Animal,
  27. Directions,
  28. Options
  29. }

注:

只有 functionclassinterface 可以直接 默认导出,其他的变量需要先定义出来,再默认导出


直接扩展全局变量

  1. interface String {
  2. prependHello(): string;
  3. }
  4. 'foo'.prependHello();

通过导入扩展全局变量

对于一个npm 包或者 UMD库的声明文件,只有 export 导出的类型声明才会有效。如果,对于这些库/包,导入之后才扩展全局变量。则需要 使用 declear global

  1. declare global {
  2. interface String {
  3. prependHello(): string;
  4. }
  5. }

十二、 内置对象

ECMAScript 标准提供的内置对象有:

BooleanErrorDateRegExp 等。

  1. let b: Boolean = new Boolean(1);
  2. let e: Error = new Error('Error occurred');
  3. let d: Date = new Date();
  4. let r: RegExp = /[a-z]/;

DOM 和 BOM 的内置对象

DocumentHTMLElementEventNodeList

  1. let body: HTMLElement = document.body;
  2. let allDiv: NodeList = document.querySelectorAll('div');
  3. document.addEventListener('click', function(e: MouseEvent) {
  4. // Do something
  5. });

====> 待续 TypeScript 进阶