————————————————
本文为CSDN博主「菜鸡的全栈路」的原创文章,原文链接:https://blog.csdn.net/qq_39403733/article/details/93807519 https://blog.csdn.net/qq_39403733/article/details/93756705
转载过来根据个人习惯以及理解 对文章有所改动,原版更清晰有条理 请转至看原文。
ts是js的高级,更偏向后端语言,更偏向面向对象 ,解决了js的很多缺陷:如弱类型,没有命名空间,不适合大型项目等
ts 的编译安装:npm install -g typescript ----> tsc指向结尾的文件
在vscode上的终端:tsc --init ----->run Task ------>选中typescript------>tsc:watch
**
**
1 写在开始之前,为什么要学习TS
vue2.x中的组件是通过声明的方式传入一系列option,和TypeScript的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。
而3.0修改了组件的声明方式,改成了类式的写法,这样使得和TypeScript的结合变得很容易。
此外,vue的源码也改用了TypeScript来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理,如React使用的Flow,Angular使用的TypeScript。现在vue3.0也全面改用TypeScript来重写了,更是使得对外暴露的api更容易结合TypeScript。静态类型系统对于复杂代码的维护确实很有必要。
因此,我觉得TS对于前端从业者也是一个必须的基本技能。
幸运的是学习过java、c++等静态语言,对于TS引入的一些概念不至于晦涩难解。
本篇文章总结TS的基础部分。
2 什么是TS
TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。
它的第一个版本发布于 2012 年 10 月,经历了多次更新后,现在已成为前端社区中不可忽视的力量,不仅在 Microsoft 内部得到广泛运用,而且 Angular2、Vue3 也都使用了 TypeScript 作为开发语言。
3 TS优缺点
优点:
TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可
即使没有显式的定义类型,也能够自动做出类型推论
可以定义从简单到复杂的几乎一切类型
即使 TypeScript 编译报错,也可以生成 JavaScript 文件
兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取
类型系统增加了代码的可读性和可维护性
拥有活跃的社区,并且支持ES6规范
不足:
对没有接触过静态语言的同学有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等概念
短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本
集成到构建流程需要一些工作量
可能和一些库结合的不是很完美
安装使用
TypeScript 的命令行工具安装方法如下:
npm install -g typescript
以上命令会在全局环境下安装 tsc 命令,安装完成之后,我们就可以在任何地方执行 tsc 命令了。
编译一个 TypeScript 文件:tsc hello.ts
此时会生成一个编译好的文件 hello.js,TypeScript 只会进行静态检查,如果发现有错误,编译的时候就会报错(仍然会生成js文件)。
4 基础
JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol。
布尔值:
使用 boolean 定义布尔值类型:let isDone: boolean = false;
注意:构造函数 Boolean 创造的对象不是布尔值:而是一个 Boolean 对象:
let createdByNewBoolean: boolean = new Boolean(1);
// index.ts(1,5): error TS2322: Type ‘Boolean’ is not assignable to type ‘boolean’.
let createdByNewBoolean: Boolean = new Boolean(1); //无错误
直接调用 Boolean 也可以返回一个 boolean 类型:
let createdByBoolean: boolean = Boolean(1); //无错误
在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样。
数值:
使用 number 定义数值类型
注意:ES6的进制表示法都会被TS编译为10进制,非ES6进制按原进制编译。
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010; // ES6 中的二进制表示法
let octalLiteral: number = 0o744; // ES6 中的八进制表示法
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
编译后:(js)
var decLiteral = 6;
var hexLiteral = 0xf00d;
var binaryLiteral = 10; // ES6 中的二进制表示法
var octalLiteral = 484; // ES6 中的八进制表示法
var notANumber = NaN;
var infinityNumber = Infinity;
字符串:
使用 string 定义字符串类型:
let myName: string = ‘tan’;
编译后:let myName = “tan”;
空值:
JS 没有空值(Void)的概念,在 TS 中,可以用 void 表示没有任何返回值的函数:
function alertName(): void {
alert(‘My name is tan’);
//没有return
}
void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:let unusable: void = undefined;
Null 和 Undefined:
在 TS 中,可以使用 null 和 undefined 来定义这两个原始数据类型:
let u: undefined = undefined;
let n: null = null;
undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null。
但undefined 和 null 是所有类型的子类型。就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
let num: number = undefined;
注意:void 类型的变量不能赋值给 number 类型的变量。
任意值:
任意值(Any)用来表示允许赋值为任意类型,因此可以赋值为不同类型的值:
let myFavoriteNumber: any = ‘seven’;
myFavoriteNumber = 7;
任意值上访问任何属性都是允许的,也允许调用任何方法
let anyThing:any = ‘hello’
console.log(anyThing.myName)//访问属性
console.log(anyThing.myName.FirstName)//访问属性
anyThing.setName(‘lilili’) //调用方法
anyThing.setName(‘lilili’).sayHello(‘hi’) //调用方法
anyThing.myName.setFirstName(‘lili’)
可以理解为:
声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
并且,变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something;
something = ‘seven’;
something = 7;
**
**
类型推断:
如果没有明确的指定类型,TS会依照类型推论(Type Inference)的规则推断出一个类型:
let myFavoriteNumber = ‘seven’; //无错
myFavoriteNumber = 7; //Type ‘number’ is not assignable to type ‘string’.
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断为‘any’类型而完全不被类型检查
let a;
a=‘one’;
a=1; //不会报错
注意:与声明不赋值区分,声明不赋值会设为任意值类型:any
联合类型
联合类型使用 | 分隔每个类型,表示可以赋值为其中的一种。
let myFavoriteNumber: string | number; //允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型。
myFavoriteNumber = ‘seven’;
myFavoriteNumber = 7; // 无错
myFavoriteNumber = true; // 报错
注意:当 TS 不确定一个联合类型的变量到底是哪个类型的时候(比如定义一个方法时),我们只能访问此联合类型的所有类型里共有的属性或方法:
function getLength(something: string | number): number { //something是形参后面的number是返回值类型
return something.length; // Property ‘length’ does not exist on type ‘string | number’.
// length 不是 string 和 number 的共有属性,所以会报错。
// 但访问 string 和 number 的共有属性不会报错,如:return something.toString();
function getLength(something: string | number): string {
return something.toString();
当联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型,此时访问该类型的属性不会报错。
对象的类型:接口
在 TS 中,使用接口(Interfaces)来定义对象的类型。
接口Interfaces是对行为的抽象,而具体如何行动是由类classes去实现(implement)。
TS 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
如:定义一个接口 Person,接着定义一个变量 tan,它的类型是 Person。这样,就约束了 tan 的形状必须和接口 Person 一致(多一些或少一些属性都会报错):
interface Person {
name: string;
age: number;
}
let tan: Person = {
name: ‘tan’,
age: 22
};
可选属性(仍然不允许添加接口中没有的属性):
当一个接口中有些属性是不需要的,将其设为可选属性,就可以不要它:
interface Person {
name: string;
age?: number; //设为可选属性
}
let tan: Person = {
name: ‘tan’
};
任意属性:*************************************************889999999999999999
注意:如果有任意属性(包括确定属性和可选属性),那么确定属性和可选属性的类型都必须是任意属性的类型的子集。
interface Person {
name: string;
[propName: string]: any; //定义了任意属性,取 string 类型的值。
}
let tom: Person = {
name: ‘tan’,
gender: ‘male’
};
一个接口中只能定义一个任意属性,如果接口中有多个类型的属性,则可以在任意属性中使用联合类型
[propName: string]: string|number; //一般写any any包括确定和可选类型的值
只读属性:
如果要求对象中的一些字段只能在创建的时候被赋值,使用 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; // Cannot assign to ‘id’ because it is a constant or a read-only property.
数组的类型:
TS中,数组有多种定义方式。
方法一:使用「类型 + 方括号」来表示数组(类型可以是any):
let fibonacci: number[] = [1, 1, 2, 3, 5]; //数组的值只能是number类型
let list: any[] = [‘Xcat Liu’, 25]; //数组的值可以是任意类型
注意:数组的项不允许出现其他类型,并且数组的一些方法的参数也不能出现其他类型,如:push()
方法二:数组泛型:let fibonacci: Array = [1, 1, 2, 3, 5]; //number可以换成any
方法三:用接口描述数组:
interface NumberArray {
[index: number]: number; //只要 index 的类型是 number,那么值的类型必须是 number。
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
方法四:类数组:
类数组(Array-like Object)不是数组类型,比如 arguments。
function sum() {
let args: number[] = arguments;//错误,arguments缺少属性push
}
// index.ts(2,7): error TS2322: Type ‘IArguments’ is not assignable to type ‘number[]’.
// Property ‘push’ is missing in type ‘IArguments’.
事实上常见的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
function sum() {
let args: IArguments = arguments;
}
函数的类型:
在 JS 中,有两种常见的定义函数的方式——函数声明(Function Declaration)和函数表达式(Function Expression)。
函数声明:
注意:参数过多或参数不足会报错
function sum(x: number, y: number): number {
return x + y;
}
函数表达式:
注意:
在 TS 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
在 ES6 中,=> 是箭头函数。
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
用接口定义函数的形状:
接口可以描述对象,数组,同样适用于函数:
interface SearchFunc { //定义一个接口
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc; //定义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’);
参数默认值:
在ES6中,允许给函数的参数添加默认值,TS 会将添加了默认值的参数识别为可选参数,因此可选参数可以不必须接在必需参数后面。
function buildName(firstName: string = ‘Tom’, lastName: string) {
return firstName + ’ ’ + lastName;
}
let tomcat = buildName(‘Tom’, ‘Cat’);
let cat = buildName(undefined, ‘Cat’);
剩余参数:
同样在ES6中,可以使用 …rest 的方式获取函数中的所有剩余参数:
function push(array: any[], …items: any[]) {
items.forEach(function(item) { 将items的每一项push进array
array.push(item);
});
}
let a = [];
push(a, 1, 2, 3); // a是一个数组,…items就是剩余的参数1,2,3
函数重载:
函数重载允许一个函数接受不同数量或类型的参数,并作出不同的处理。
注意:TS 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。如:
//定义三个重载函数,实现数字或字符串的反转
//虽然利用联合类型可以实现此功能,但使用函数重载更能精准表达!使用时可以看到三个此函数的提示。
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(’’);
}
}
枚举
类
abstract用于定义抽象类和其中的抽象方法, 抽象类不允许被实例化,抽象类中的抽象方法被续杯子类实现
abstract class Animal{
public name;
public construct (name){
this.name=name;
}
public abstract sayHi();
}
class Cat extends Animal{
public eat(){
console.log(’${this.name} is eating’)
}
public sayHi(){
console.log('hi,myname is ${this.name} ');
}
}
let cat = new Ca(‘miao’);
类型断言:
类型断言(Type Assertion)是开发者手动指定一个值的类型:
<类型>值或值 as 类型
注意:在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用值 as 类型。
例如:将一个联合类型的变量指定为一个更加具体的类型(但不能指定为联合类型中不存在的类型):
// 使用联合类型时,必须使用这些类型共有的属性才行,但使用类型断言使其确定为某一种类型,就没有了此限制。
function getLength(something: string | number): number {
if ((something).length) { //something类型断言为字符串,就可以使用字符串独有的属性方法。
return (something).length;
} else {
return something.toString().length;
}
}
注意:类型断言不是类型转换。
内置对象
JS 中有很多内置对象,这些内置对象可以直接在 TS 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
ECMAScript 的内置对象:
ECMAScript 标准提供的内置对象有:Boolean、Error、Date、RegExp 等。更多的内置对象。
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 的内置对象:
DOM 和 BOM 提供的内置对象有:Document、HTMLElement、Event、NodeList 等。
TS 中使用这些类型:
let body: HTMLElement = document.body;
let allDiv: NodeList = document.querySelectorAll(‘div’);
document.addEventListener(‘click’, function(e: MouseEvent) {
// Do something
});
TypeScript 核心库的定义文件:
TypeScript 核心库的定义文件中定义了所有浏览器环境需要用到的类型,并且是预置在 TS 中的。比如常用的Math.pow(),已经被预定义好类型:
interface Math {
pow(x: number, y: number): number;
}
注意,TypeScript 核心库的定义中不包含 Node.js 部分。
用 TypeScript 写 Node.js:
Node.js 不是内置对象的一部分,所以要用 TS 写 Node.js就则需要引入第三方声明文件:npm install @types/node --save-dev
声明文件
当使用第三方库时,需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。具体见声明文件章节。