什么是TypeScript
TypeScript是JavaScript的一个超集,主要提供了类型系统和对ES6的支持。
TS的优势
-
增加了代码的可读性和可维护性 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了 可以在编译阶段就发现大部分错误 增强了编辑器和IDE的功能,包括代码补全,接口提示,跳转到定义,重构等
-
ts非常包容 即使不显式的定义类型,也能够自动做出类型推导; 可以定义从简单到复杂的几乎一切类型; 即使ts编译报错,也可以生成js文件; 兼容第三方库,即使第三方库不是用ts写的,也可以编写单独的类型文件供ts读取
-
活跃的社区
TS的缺点
- 有一定的学习成本,需要理解接口(Interfaces), 泛型(Generics), 类(Classes), 枚举类型(Enums)等前端不熟悉的概念
- 短期增加开发成本,但可以减少后期维护成本
- 集成到构建流程需要一定的工作量
- 可能和一些库结合的不是很完美
安装TS
npm i typescript -g
以上命令会在全局环境下安装tsc
命令,安装完成后,我们就可以在任何地方执行tsc
命令了。
tsc hello.ts
小demo
// hello.ts
function sayHello(person: string) {
return 'Hello, ' + person;
}
let user = 'Tom';
console.log(sayHello(user));
执行tsc hello.ts
会:
//hello.js
function sayHello(person) {
return 'Hello, ' + person;
}
var user = 'Tom';
console.log(sayHello(user));
ts只会进行静态检查,如果发现有错误,编译的时候就会报错
如果在报错的时候终止js文件的生成,可以在tsconfig.json
中配置noEmitOnError
就可以了。
原始数据类型
布尔值
let isDone: boolean = false
数值
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
字符串
let myName: string = 'Tom'
let sentence: string = `hello, my name is ${myName}`
空值
JS中没有空值(void)的概念, 在ts中,可以用void
表示没有任何返回值的函数
function alertName(): void {
alert('my name is Tom')
}
声明一个void
类型的变量没有什么用,因为只能讲它赋值为undefined
和null
let unusable: void = undefined
Null和Undefined
let u: undefined = undefined
let n: null = null
与void的区别是, undefined和null是所有类型的子类型。也就是说undefined类型的变量,可以赋值给number类型的变量:
let num: number = undefined
//这样不会报错
let u: undefined
let num: number = u
//这样也不会报错
let u: void
let num: number = u
//Type 'void' is not assignable to type 'number'.
任意值
任意值(Any)用来表示允许赋值为任意类型
如果是一个普通类型,在赋值过程中改变类型是不被允许的,但如果是any
类型,则被允许赋值为任意类型:
let str: string = 'sens'
str = 5
// Type 'number' is not assignable to type 'string'
let str: any = 'sens'
str = 5
// ok
在任意值上访问任何属性都是允许的,也允许调用任何方法:
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');
可以认为, 声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
未声明类型的变量
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型。
类型推论
ts会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查
联合类型
联合类型表示取值可以为多种类型中的一种
let str: string | number
联合类型使用|
分隔每个类型
访问联合类型的属性和方法
当ts不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:
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'
对象的类型 -- 接口
在ts中,使用接口(interfaces)来定义对象的类型
什么是接口
在面向对象的语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。
interface Person {
name: strin;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 23
}
接口一般首字母大写。
定义的变量比接口少了一些属性是不被允许的,多一些属性也是不允许的。赋值的时候, 变量的形状和接口的形状必须保持一致
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
name: string;
age?: number;
}
任意属性
有时我们希望一个接口允许有任意的属性:
interface Person {
name: string;
age?: number;
[propName: string]: any
}
// 使用[propName: string]定义了任意属性取string类型的值
需要注意,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
//报错,因为age是number类型不是strin的子类型
只读属性
interface Person {
readonly: id: number;
name: string;
age: number;
[propName: string]: any
}
注意:只读的约束在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。
数组的类型
在ts中,数组类型有多种定义方式,比较灵活。
let fib: number[] = [1,2,3]
//数组的项中不允许出现非number类型
- 数组泛型
let fib: Array<number> = [1,2,3]
- 用接口表示数组
interface NumberArray {
[index: number]: number
}
//上面的定义表示: 只要索引的类型是数字时,那么值得类型必须是数字
let fib: NumberArray = [1,2,3]
- 类数组
function sum() {
let args: number[] = arguments
}
//报错,arguments是一个类数组,不能用普通的数组定义,而是使用接口:
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
事实上,常用的类数组都有自己的接口定义, 如IArguments, NodeList, HTMLCollection等:
function sum() {
let args: IArguments = arguments
}
any在数组中的应用
let list: any[] = ['dd', 2, {ss: 'cc'}]
函数的类型
//函数声明
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;
};
//注意: 不要混淆了ts中的箭头和es6中的箭头
//在ts中箭头表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
注意: 输入多余的参数是不被允许的。
用接口定义函数的形状
interface Search {
(source: string, sub: string): boolean
}
let mySearch: Search
mySearch = function(source: string, sub: string) {
return boolean
}
可选参数
前面强调输入多余(少于)参数,是不允许的,但可以用?
表示可选参数:
function buildName(firstName: string, lastName?: string) {
if (lastName) {
return firstName + ' ' + lastName;
} else {
return firstName;
}
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
注意: 可选参数必须在必需参数后面,换句话说,可选参数后面不允许再出现必须参数了。
参数默认值
ts会将添加了默认值的参数识别为可选参数。
剩余参数
es6中,可以用...
的方式获取函数中的剩余参数:
function push(array: any[], ...items: any[]){}
重载
可以使用重载定义多个reverse
的函数类型:
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('');
}
}
上例中,我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意: ts会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
两种写法:
<类型>值
值 as 类型
//react只能使用第二种
function getLength(something: string | number): number {
if ((something as string).length) {
return something as string).length;
} else {
return something.toString().length;
}
}
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码不全,接口提示等功能。
声明文件必须以.d.ts
为后缀
当一个第三方库没有提供声明文件时,我们就需要自己书写声明文件了。
库的主要使用场景:
- 全局变量: 通过