TypeScript学习手册

274 阅读12分钟

typeScript

安装方式

npm install -g typescript

检测版本号

cmd/ tsc -v

优缺点

优点

  1. 增加了代码的可读性和可维护性
    • 类型系统实际上是最好的文档,大部分函数看看类型定义就知道如何使用了
    • 可以在编译阶段就发现大部分错误,这总比在运行的时候出错好
    • 增强了编译器和IDE,包括代码补全,接口提示,跳转定义,代码重构
  2. 非常包容
    • 文件后缀为.ts
    • 及时不显示的定义类型,也能够自动做出类型推论
    • 编译报错的时候也会生成js文件
    • 兼容第三方库,及时第三方空不是用ts写的,也可以编写单独的类型文件提供ts读取
  3. 活跃的社区
    • 大把的三方库都有提供给ts的类型定义文件
    • angular、vue、vscode、Ant Design等耳熟能祥的项目就是用的ts
    • 可以使用ES6

缺点

  1. 有一定的学习成本,需要理解新的内容

    • 接口(Interfaces
    • 泛型(Generics
    • 类(Classes
    • 枚举类型(Enums)等前端工程师可能不是很熟悉的概念
  2. 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本

  3. 成到构建流程需要一些工作量

基础

原始数据类型

JavaScript 的类型分为两种:原始数据类型和对象类型(Object types)。

原始数据类型包括:布尔值、数值、字符串、nullundefined 以及 ES6 中的新类型 SymbolBigInt

boolean/Boolean

布尔值是最基础的数据类型,在TypeScript中,使用boolean定义布尔值类型

  	let isTool: boolean = false;
	// 编译通过// 后面约定,未强调编译错误的代码片段,默认为编译通过

注意,使用构造函数Boolean创造的对象不是布尔值

  	let isTool2: 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.

事实上new Boolean()返回的是一个boolean对象:

	let tool3: Boolean = new Boolean(1);

直接调用Boolean也可以返回一个boolean类型:

	let tool4: boolean = Boolean(1);

在 TypeScript 中,booleanJavaScript 中的基本类型,而 BooleanJavaScript 中的构造函数。其他基本类型(除了 null undefined)一样,不再赘述。

Number

使用 number 定义数值类型:

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;

编译结果:

	var decLiteral = 6;
	var hexLiteral = 0xf00d;
	// ES6 中的二进制表示法
	var binaryLiteral = 10;
	// ES6 中的八进制表示法
	var octalLiteral = 484;
	var notANumber = NaN;
	var infinityNumber = Infinity;

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

string

使用 string 定义字符串类型:

	let myName: string = 'Tom';
	let myAge: number = 25;

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

编译结果:

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

空值

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

	function alertName(): void {
	    alert('My name is Tom');
	}

声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefinednull

	let unusable: void = undefined;

编译结果:

	var unusable = undefined;

NullUndefined

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

	let u: undefined = undefined;
	let n: null = null

void 的区别是,undefinednull 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:

	// 这样不会报错
	let num: number = undefined;

	// 这样也不会报错
	let u: undefined;
	let num: number = u;

编译结果:

	var num = undefined;
	// 这样也不会报错
	var u;
	var num2 = u;

void 类型的变量不能赋值给 number 类型的变量:

	let u: void;
	let num: number = u;

这样会直接报错,编译结果为

	var u;
	var num = u;

任意值

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

什么是任意值类型

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

	let myFavoriteNumber: string = 'seven';
	myFavoriteNumber = 7;
	// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

但如果是 any 类型,则允许被赋值为任意类型。

	let myFavoriteNumber: any = 'seven';

任意值的属性和方法

在任意值上访问任何属性都是允许的:

	let anyThing: any = 'hello';
	console.log(anyThing.myName);
	console.log(anyThing.myName.firstName);

也允许调用任何方法:

	let anyThing: any = 'tom';
	anyThing.setName('jerry');
	anyThing.setName('jerry').sayHello();
	anyThing.myName.setFirstName('cat');

可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。

未声明类型的变量

变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:

	let something;
	something = 'seven';
	something = 7;

	something.setName('Tom');

等价于

	let something: any;
	something = 'seven';
	something = 7;

	something.setName('Tom');

类型推论

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

什么是类型推论

	let myFavoriteNumber = 'seven';
	myFavoriteNumber = 7;
	// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

事实上,它等价于:

	let myFavoriteNumber: string = 'seven';
	myFavoriteNumber = 7;
	// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.

TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

	let myFavoriteNumber;
	myFavoriteNumber = 'seven';
	myFavoriteNumber = 7;

联合类型

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

简单的例子

   let myFavoriteNumber: string | number;
   myFavoriteNumber = 'seven';
   myFavoriteNumber = 7;
	let myFavoriteNumber: string | number;
	myFavoriteNumber = true;

联合类型使用|分隔每个类型。

这里的let myFavoriteNumber: string | number;的含义是,允许myFavoriteNumber的类型是string或者number,但是不能是其他类型。

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

当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'.

上例中,length不是stringnumber的共有属性,所以会报错。

访问stringnumber的共有属性是没问题的:

	function getString(something: string | number): string {
	    return something.toString();
	}

联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:

	let myFavoriteNumber: string | number;
	myFavoriteNumber = 'seven';
	console.log(myFavoriteNumber.length); // 5
	myFavoriteNumber = 7;
	console.log(myFavoriteNumber.length); // 编译时报错
	// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.

上例中,第二行myFavoriteNumber被推断成了string,访问它的length属性不会报错。而第四行myFavoriteNumber被推断成了number,访问length属性就会报错了。

对象的类型——>接口

在TypeScript中,我们使用接口来定义对象的类型。

什么是接口

在面对对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要类(class)去实现(implement)。

TypeScript中的接口是一个非常灵活的概念,出了可用于对类的一部分行行为进行抽象以外,也常用于[对象的形状(shaoe)]进行描述。

简单的例子

   interface Person {
   	 name: string;
   	 age: number;
   }
   let tom: Person = {
   		name: 'tom',
   		age: 25
   }

上面的例子中,我们定义了一个接口person,接着定义了一个变量tom,它的类型是Person。 这样, 我们就约束了tom的形状必须和接口person一致。

接口一般首字母大写。有点变成语言中中会建议接口的名称加上I前缀。

定义的变量比接口少了一些属性是不允许的:

	interface Person {
	    name: string;
	    age: number;
	}

	let tom: Person = {
	    name: 'Tom'
	};
	// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
	//   Property 'age' is missing in type '{ name: string; }'.

多一些属性也是不允许的:

	interface Person {
	    name: string;
	    age: number;
	}

	let tom: Person = {
	    name: 'Tom',
	    age: 25,
	    gender: 'male'
	};

	// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
	//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

可见,赋值的时候,变量的形状必须和接口的形状保持一致。

可选属性

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

	interface Person {
		name: string;
		age?: number;
	}
	let tom: Person = {name:'tom'}
	interface Person {
		name: string;
		age?: number;
	}
	let tom: Person = {
		name: 'tom',age: 25
	}

可选属性的含义是该属性可以不存在。

这时__仍然不允许添加未定义的属性:__

	interface Person {
		name: string;
		age?: number;
	}
	let tom: Person = {
		name: 'tom',age: 25, gender: 'male'
	}
	// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
	//   Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

任意属性

有适合我们希望一个接口允许有任意的属性,可以使用如下方式:

	interface Person {
		name: string;
		age?: number;
		[propName: string]: any;
	}
	let tom: Person = {
		name: 'tom', gender: 'male'
	}

使用[propName: string]定义了任意属性取string类型的值。

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:

	interface Person {
	    name: string;
	    age?: number;
	    [propName: string]: string;
	}
	let tom: Person = {name: 'Tom',age: 25,gender: 'male'};
	// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
	// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
	//   Index signatures are incompatible.
	//     Type 'string | number' is not assignable to type 'string'.
	//       Type 'number' is not assignable to type 'string'.

上例中,任意属性的值允许是string,但是可选属性age的值确实number,number不是string的子属性,所以报错了。

另外,在报错信息中可以看出,此时{name: 'tom',age: 25,gender: 'male'}的类型被推断成了{[x: string]: string | number; name: string; age: number; gender: string;},这是联合类型和接口的结合。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

	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:4864,name: 'tom', gender: 'male'
	}
	tom.id = 4864;
	// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

上例中,使用readonly定义的属性id初始化后,又被赋值了,所以报错了。

主要,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:

	interface Person {
		readonly id: number;
		name: string;
		age?: number;
		[propName: string]: any;
	}
	let tom: Person = {
		name: 'tom', gender: 'male'
	}
	tom.id = 4864;
	// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
	//   Property 'id' is missing in type '{ name: string; gender: string; }'.
	// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

上例中,报错信息有两处,第一处是在对tom进行赋值的时候,没有给id赋值。

第二处是在给tom.id赋值的时候,有雨它是只读属性,所以报错了。

数组的类型

在TypeScript中,数组类型有多种定义方式,比较灵活。

【类型 + 方括号】表示法

最简单的方式的使用[类型 + 方括号]来表示数组:

	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'.

上例中,push方法只允许传入number类型的参数,但是却传了一个"8"类型的参数,所以报错了。这里"8"是一个字符串字面量类型

数组泛型

也可以使用数组泛型Array<elemType>来表示数组:

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

用接口表示数组

接口也可以用来描述数组:

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

NumberArray表示:只要索引的类型是数字时,那么值的类型必须是数字。 虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。

不过有一种情况例外,那就是他常用来表示类数组。

类数组

类数组不是数组类型,比如arguments

	function sum(){
		let args: number[] = arguments;
	}
	// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.

上例中,arguments实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:

	function sum(){
		let args: {
			[index: number]: number;
			length: number;
			callee: Function;
		} = arguments;
	}

在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有lengthcallee两个属性。

事实上常用的类数组都有自己的接口定义,如,IArguments,NodeList,HTMLCollection等:

	let args: IArguments = arguments;

其中IArguments是TypeScript中定义好了的类型,它实际上就是:

	interface IArguments {
		[index: number]: any;
		length: number;
		callee:Function;
	}

any在数组中的应用

一个比较常见的做法是,用any表示数组中允许出现任意类型:

	let list:any[] = ['lcy',25,{website:'http://www.baidu.com'}];