typescript快速上手
简介
参考文档
- https://ts.xcatliu.com/ 阮一峰typescript入门教程
- TypeScript: JavaScript With Syntax For Types. (typescriptlang.org) typescript官网
什么是TypeScript
TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持,它由 Microsoft 开发,代码开源于 GitHub 上。
它的第一个版本发布于 2012 年 10 月,经历了多次更新后,现在已成为前端社区中不可忽视的力量,不仅在 Microsoft 内部得到广泛运用,而且 Google 开发的 Angular 从 2.0 开始就使用了 TypeScript 作为开发语言,Vue 3.0 也使用 TypeScript 进行了重构。
总结
- TypeScript 是添加了类型系统的 JavaScript,适用于任何规模的项目。
- TypeScript 是一门静态类型、弱类型的语言。
- TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性。
- TypeScript 可以编译为 JavaScript,然后运行在浏览器、Node.js 等任何能运行 JavaScript 的环境中。
- TypeScript 拥有很多编译选项,类型检查的严格程度由你决定。
- TypeScript 可以和 JavaScript 共存,这意味着 JavaScript 项目能够渐进式的迁移到 TypeScript。
- TypeScript 增强了编辑器(IDE)的功能,提供了代码补全、接口提示、跳转到定义、代码重构等能力。
- TypeScript 拥有活跃的社区,大多数常用的第三方库都提供了类型声明。
- TypeScript 与标准同步发展,符合最新的 ECMAScript 标准(stage 3)。
安装 TypeScript
1 | npm install -g typescript |
以上命令会在全局环境下安装 tsc
命令,安装完成之后,我们就可以在任何地方执行 tsc
命令了。
编译一个 TypeScript 文件很简单:
1 | tsc hello.ts |
我们约定使用 TypeScript 编写的文件以 .ts
为后缀,用 TypeScript 编写 React 时,以 .tsx
为后缀。
Hello TypeScript
首先编写 hello.ts
1 | function sayHello(person: string) { |
然后执行编译命令
1 | tsc hello.ts |
这时候会生成一个 hello.js
,内容如下
1 | function sayHello(person) { |
在 TypeScript 中,我们使用 :
指定变量的类型,:
的前后有没有空格都可以。
上述例子中,我们用 :
指定 person
参数类型为 string
。但是编译为 js 之后,并没有什么检查的代码被插入进来。
这是因为 TypeScript 只会在编译时对类型进行静态检查,如果发现有错误,编译的时候就会报错。而在运行时,与普通的 JavaScript 文件一样,不会对类型进行检查。
现在我们来修改一下 hello.ts
1 | function sayHello(person: string) { |
然后重新执行编译命令,发现报如下错误
但是 hello.js
文件仍然正常生成
1 | function sayHello(person) { |
这是因为 ts 只会在编译时报错,但是在运行时不会提示错误
基础
原始数据类型
JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
原始数据类型包括:布尔值、数值、字符串、null
、undefined
以及 ES6 中的新类型 Symbol
和 ES10 中的新类型 BigInt
。
布尔值(boolean)
布尔值是最基础的数据类型,在 TypeScript 中,使用 boolean
定义布尔值类型
1 | let boolean: boolean = false |
数值(number)
使用 number
定义数值类型
1 | let num1: number = 123 |
字符串(string)
使用 string
定义字符串类型
1 | let str1: string = 'hello' |
空值(void)
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void
表示没有任何返回值的函数
1 | function returnVoid(): void { |
null 和 undefined
在 TypeScript 中,可以使用 null
和 undefined
来定义这两个原始数据类型
1 | let n: null = null |
任意类型
声明方式
任意类型使用 any 关键字来表示。原始数据类型声明过类型后不能再赋值其他类型的数据,而任意值的类型可以赋值成多种数据类型
1 | let anystr: any = 'hello' |
任意值的属性和方法
任意值属性可以访问任意方法和属性
1 | // 下面写法不会出错 |
可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
未声明类型变量
如果一个变量没有声明类型时,则默认也是任意值类型
1 | let something; |
等同于
1 | let something: any; |
类型推论
下面的代码虽然没有声明变量的类型,但是会报错
1 | let myFavoriteNumber = 'save' |
这个代码等同于
1 | let myFavoriteNumber: string = 'save' |
如果给变量声明了初始值,则ts会根据设置初始值类型推测出这个变量的数据类型,这就是类型推论
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any
类型而完全不被类型检查:
1 | let notSetInitVal |
联合类型
表示取值可以是多种类型中的一种
简单的举例
1 | let str4: number | string = 56 |
多个类型使用 |
分隔,赋值时可以是声明了的类型,不能声明额外的数据类型
访问联合类型的属性或方法
当联合类型属性的值不确定时,只能访问联合类型中共用的属性或者方法,下面代码访问 num.toFixed(2)
出错是因为 string 类型没有这个方法
1 | // 访问联合类型的属性或方法 |
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型
1 | let str6: string | number |
上面的代码首先类型推断为字符串类型,所以可以使用 length 属性,然后又赋值为数字类型,所以可以使用 toFixed 方法
对象的类型-接口
什么是接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。
简单的例子
1 | interface Person { |
上面的例子中我们声明了一个接口 Person,并规定里面有两个属性,一个是 string 类型的 name,一个是 number 类型的 age。然后什么一个 str7 来实现这个接口,实现接口的对象的形状必须和接口一致,多一个属性或者少一个属性都不行
多一个时:
少一个时:
可见,赋值的时候,变量的形状必须和接口的形状保持一致
可选属性
有时我们希望不要完全匹配一个形状,那么可以用可选属性。
1 | interface Status { |
在属性名称后面添加一个 ?
表示这是一个可选属性,实现这个接口时可以不声明这个属性,但是此时添加额外属性还是不行的
任意属性
有时候我们希望一个接口允许有任意的属性,可以使用如下方式
1 | interface Order { |
注意:如果定义了任意属性,则其他属性的类型都必须是任意属性类型的子集。所以上面的接口中定义的任意属性的类型为 any
只读属性
如果希望一个属性只能在初始化时赋值,然后不能更改。可以使用 readonly 关键字定义只读属性
1 | interface ReadOnly { |
数组的类型
在 typescript 中,数组类型有多中定义方法,比较灵活
类型+方括号表示法
声明了数组类型后,数组中不能有其他类型的数据,这是最常用的表达方式
1 | // number 类型的数组 |
数组的一些方法参数也会收到类型影响
1 | // 类型“number”的参数不能赋给类型“string”的参数 |
数组泛型
1 | let fibonacci: Array<number> = [1, 1, 2, 3, 5]; |
泛型具体讲解在后面章节
用接口标识数组
1 | interface StrArr { |
一般不这样做
类数组
类数组不是数组类型,比如 argument
1 | function sum() { |
arguments 不是一个普通的数组,所以不能用普通的数组来表示,必须使用类数组
1 | function sum() { |
事实上常用的类数组都有自己的接口定义,如 IArguments
, NodeList
, HTMLCollection
等:
1 | function sum2() { |
其中 IArguments
是 TypeScript 中定义好了的类型,它实际上就是:
1 | interface IArguments { |
关于内置对象,查看后面的章节
any 在数组中的应用
any 表示数组中可以是任意类型的值
1 | let objarr:any[] = [ |
函数类型
函数的声明
普通方式声明函数
1 | // 在typescript中当函数有输入和输出时,都要吧参数类型声明好 |
函数表达式来声明函数
1 | // ts版本的函数表达式 |
函数的参数一旦定义完成后,少传或者多传参数都是不允许的
1 | // 输入多余的参数或者少于参数都是不允许的 |
用接口定义函数形状
1 | // 用接口定义函数形状 |
可选参数
上面提到,参数声明好之后少传或者多传都是不允许的,但是实际情况中我们有的参数时有时无,这种时候我们可以使用可选参数
可选参数使用 ?
来表示
1 | const sum4 = (a: number, b: number, c?: number): number => { |
这里需要注意的是,可选参数的位置必须放在最后面,否则报错
参数默认值
1 | // 参数默认值 |
剩余参数
1 | // 剩余参数 |
方法重载
1 | // 首先定义这个方法有那几种传参和返回的可能 |
上例中,我们重复定义了多次函数 reverse
,前几次都是函数定义,最后一次是函数实现。在编辑器的代码提示中,可以正确的看到前两个提示。
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
类型断言
可以手动指定一个值的类型
语法:
1 | 值 as 类型 |
将联合类型断言为其中一个类型
前面提到,在ts中不确定一个联合类型的变量是哪一个的时候,只能访问此联合类型中所有类型共有的属性或方法
1 | // 类型断言 |
然而有时候,我们必须在不确定是那个类型的时候使用其中一个类型的属性,例如下面的代码
1 | // 类型断言 |
这个时候就要使用断言
1 | // 类型断言 |
将一个父类断言为一个具体的子类
1 | // 将一个父类断言为一个具体的子类 |
函数isOK接收的是Error类型的参数,但是Error上没有code属性,所以将err参数断言为ApiError,就可以使用code属性进行条件判断
将任何一个类型断言为any
首先看下面的代码
1 | let num: number = 1 |
当我们访问了一个类型上不存在属性或者方法时,ts会明确的给我们提示出错误原因,这一点是很有用的。
但是有时候我们必须要访问一个不存在的属性,例如:
1 | window.userId = 1001 |
我们想在window上添加一个userId属性,这个时候ts会提示错误,我们可以使用 as any,来避免错误
1 | (window as any).userId = 1001 |
注意的是这种语法会掩盖原有的错误提示,但是在开发中有时候这种写法反而可以提高开发效率
将any断言成一个具体的类型
1 | // 将any类型断言成一个具体的类型 |
类型断言的限制
- 联合类型可以被断言为其中一个类型
- 父类可以被断言为子类
- 任何类型都可以被断言为 any
- any 可以被断言为任何类型
- 要使得
A
能够被断言为B
,只需要A
兼容B
或B
兼容A
即可
声明文件
新语法索引
declare var
声明全局变量declare function
声明全局方法declare class
声明全局类declare enum
声明全局枚举类型declare namespace
声明(含有子属性的)全局对象interface
和type
声明全局类型export
导出变量export namespace
导出(含有子属性的)对象export default
ES6 默认导出export =
commonjs 导出模块export as namespace
UMD 库声明全局变量declare global
扩展全局变量declare module
扩展模块///
三斜线指令
什么是声明语句
例如我们在使用jQuery时,我们想要通过一个id获得元素,我们可以这样做
1 | $('#id') |
但是在ts中,会提示:找不到名称“jQuery”,这时候需要使用 declare var 来声明
1 | declare var jQuery: (selector: string) => any; |
上例中,declare var
并没有真的定义一个变量,只是定义了全局变量 jQuery
的类型,仅仅会用于编译时的检查,在编译结果中会被删除。它编译结果是:
1 | jQuery('#foo'); |
声明文件
通常我们会把声明专门放在一个文件中,这个文件就称为声明文件
例如新建 jQuery.d.ts
,然后里面添加代码
1 | declare var jQuery: (selector: string) => any; |
声明文件必须以 d.ts
结尾,typescript 会扫描所有以 .ts 结尾的文件,所以声明文件也会扫描到。然后再使用 jQuery 就不会出错了
当然,jQuery 的声明文件不需要我们定义了,社区已经帮我们定义好了:jQuery in DefinitelyTyped。
我们可以直接下载下来使用,但是更推荐的是使用 @types
统一管理第三方库的声明文件。
@types
的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
1 | npm install @types/jquery --save-dev |
可以在这个页面搜索你需要的声明文件
书写声明文件
内置对象
JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。
内置对象是指根据标准在全局作用域(Global)上存在的对象。这里的标准是指 ECMAScript 和其他环境(比如 DOM)的标准。
ECMAScript 的内置对象
ECMAScript 标准提供的内置对象有:
1 | Boolean`、、、 等。`Error``Date``RegExp |
我们可以在 TypeScript 中将变量定义为这些类型:
1 | let err: Error = new Error("this is a error") |
DOM 和 BOM 的内置对象
DOM 和 BOM 提供的内置对象有:
1 | Document`、、、 等。`HTMLElement``Event``NodeList |
TypeScript 中会经常用到这些类型:
1 | let body: HTMLElement = document.body; |
它们的定义文件同样在 TypeScript 核心库的定义文件中。
用 TypeScript 写 Node.js
Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:
1 | npm install @types/node --save-dev |
进阶
类型别名
使用关键词 type
来声明一个类型别名
1 | // 定义str表示string类型 |
字符串字面量类型
1 | // 声明一个类型depts,值只能是下面定义的三个部门 |
元祖
数组是吧相同类型的数据放在一个中括号中,而元祖是吧不同类型的元素放在一个中括号中
初始化赋值
1 | // 元祖 |
直接对元祖赋值的时候。需要提供元祖中的所有类型值
1 | //=> err:不能将类型“[string]”分配给类型“y”。源具有 1 个元素,但目标需要 2 个 |
越界的元素
使用 push 方法可以往元祖中追加元素,当超出初始化的长度时,则超出的元素类型将是元祖中每个类型的联合类型
1 | //=> err:类型“boolean”的参数不能赋给类型“string | number”的参数 |
TS中的!和?用法
- 属性或参数中使用 ?:表示该属性或参数为可选项
- 属性或参数中使用 !:表示强制解析(告诉typescript编译器,这里一定有值),常用于vue-decorator中的@Prop
- 变量后使用 !:表示类型推断排除null、undefined
枚举
枚举类型用于取值被限制在某一范围内的场景,比如一周只能有7天,颜色只能是红黄蓝
简单的例子
使用 enum
来定义枚举类型
1 | // 定义枚举类型 |
声明的代码编译成JS后是这样的
1 | var weekDays; |
手动赋值
1 | // 手动赋值枚举类型 |
手动赋值也可以赋值小数或者负数
1 | enum weekDays3 { Sun = -2, Mon = 1.5, Tue, Wed, Thu, Fri, Sat } |
常数项和计算所得项
我们上面定义的枚举值都是常数项,而经过计算才能得到值的项目被称为计算所得项。例如
1 | enum weekDays4 { Sun = -2, Mon = 1.5, Tue, Wed, Thu, Fri, Sat = 'sat'.length } |
计算所得项后面也必须是计算所得项或者没有项
1 | enum weekDays4 { Sun = -2, Mon = 1.5, Tue, Wed, Thu, Fri, Sat = 'sat'.length, Test = 'Test'.length } |
如何只定义常数枚举,可以使用关键字 const
1 | // 定义常数枚举 |
JS中类的用法
类的概念
虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过
new
生成 - 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如
Cat
和Dog
都继承自Animal
,但是分别实现了自己的eat
方法。此时针对某一个实例,我们无需了解它是Cat
还是Dog
,就可以直接调用eat
方法,程序会自动判断出来应该如何执行eat
- 存取器(getter & setter):用以改变属性的读取和赋值行为
- 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如
public
表示公有属性或方法 - 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
es6中类的用法
属性和方法
1 | class Animal { |
类的继承
使用关键字 extend
1 | // 类的继承 |
存取器
在类中通过 get 和 set 方法来对某个属性进行赋值和读取操作
1 | class Animal { |
由于类的概念是在 es6 之后才提出的,但是默认编译 ts 为 js 时会编译成 es5 的代码,所以在编译时可以指定编译后的js版本
指定 -t es6
即可将ts变为es6的js代码
1 | tsc .\jsClass.ts -t es6 |
静态方法
方法前面声明关键字 static
表示这个方法为一个静态方法,不需要通过实例化对象来调用,直接通过 类名.方法名()
调用
1 | class Animal { |
es7中类的用法
实例属性
es6中类里面的属性只能通过构造函数里面的 this.xxx = xxx
来定义,在es7中可以直接在类里面定义属性
1 | class Person{ |
静态属性
顾名思义,可以直接通过类来访问某个属性
1 | class Person{ |
TS中类的用法
public private 和 protected
- public 表示该属性或者方法是公共的,在类的外部也是可以被访问的,默认类中所有的方法和属性都是 public
- private 表示该属性或者方法是私有的,只允许在类内部使用
- protected 修饰的属性或方法是受保护的,它和
private
类似,区别是它在子类中也是允许被访问的
通过代码来区别三者不同
1 | class PublicClass { |
上面的代码将name属性设置成了public,所以可以在类的外部访问和赋值
1 | class PrivateClass { |
上面的代码将name设置成了private,然后实例化一个PrivateClass类对象,通过对象来访问name时,会出现错误提示,提示:属性“name”为私有属性,只能在类“PrivateClass”中访问
1 | class ProtectedClass { |
设置了 protected 修饰的属性,效果和 private 类似,但是 protected 修饰的属性或方法允许在子类中使用
readOnly
readOnly表示只读属性,无法修改这个属性的值
1 | class ReadOnlyClass { |
注意:如果 readonly
和其他访问修饰符同时存在的话,需要写在其后面。
1 | class Animal { |
抽象类
abstract 用于定义抽象类的抽象方法,抽象类不允许被实例化,抽象方法必须被子类实现
1 | abstract class AbstractClass{ |
当实现一个抽象类时会出现错误提示
除此之外,抽象方法必须被子类实现,代码如下
当没有实现抽象方法时会出现下面的错误提示
1 | abstract class AbstractClass{ |
必须实现父类的抽象方法
1 | abstract class AbstractClass { |
类的类型
给类中的属性和方法添加类型限制很简单
1 | class ClassType { |
类与接口
类实现接口
一般来讲,一个类可以继承另外一个类,多个类之间会存在相同的东西,我们可以吧这些相同的功能抽取成一个接口,让类去实现它。
举个例子:门的一个父类,防盗门是门的一个子类,现在我要给防盗门添加一个报警器,我可以实现一个报警器的接口,让防盗门去实现它,现在我又有一个车,车上面也要有报警器功能,我直接让车去实现报警器接口即可
代码实现
1 | // 报警器接口 |
一个类可以实现多个接口
例如:现在除了实现报警器功能,再实现一个车灯的功能
1 | // 报警器接口 |
接口继承接口
车灯接口继承报警器接口后,类去实现车灯接口时,也还是要实现三个方法
1 | // 报警器接口 |
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
简单的例子
1 | function createArray(total: number, value: any): Array<any> { |
这段代码中,valarr 数组中的每一项都是任意类型,现在我们想让数组中的元素类型是我们输入的数据类型,这时候泛型就派上用场了
1 | // 添加泛型约束 |
也可以不手动指定泛型,让类型推断自动推断出来数据类型
多个类型参数
1 | function swap<T, U>(tuple: [T, U]): [U, T] { |
上面的代码会自动推断为:function swap<*number*, *string*>(tuple: [number, string]): [string, number]
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道属性的具体类型,所以读取一些属性的时候会报错
1 | function getLength<T>(val:T){ |
可以通过让泛型继承某个接口
1 | interface witchLenth { |
多个类型互相约束
1 | // 多个类型之间互相约束 |
泛型接口
使用泛型接口来约束函数形状
1 | // 我们可以使用泛型接口来定义函数形状 |
可以吧泛型提前到接口上
1 | interface CreateArrFun3<T> { |
泛型类
1 | class GenerClass<T>{ |
泛型参数的默认类型
1 | // 泛型的默认类型 |
声明合并
如果声明了同名的函数、接口、类,那么他们会合并成一个类型
函数的合并
我们可以使用重载,定义多个函数类型
1 | function reverse(val: number): number |
接口合并
1 | interface Alarm { |
这里接口合并时,当属性出现重复时,必须保证重名的属性类型一致,否则会报错
1 | interface Alarm { |
工程
在 TypeScript 中使用 ESLint
安装Eslint
将下面的依赖安装在当前项目中
1 | npm install --save-dev eslint |
由于 ESLint 默认使用 Espree 进行语法解析,无法识别 TypeScript 的一些语法,故我们需要安装 @typescript-eslint/parser
,替代掉默认的解析器,别忘了同时安装 typescript
:
1 | npm install --save-dev typescript @typescript-eslint/parser |
接下来需要安装对应的插件 @typescript-eslint/eslint-plugin 它作为 eslint 默认规则的补充,提供了一些额外的适用于 ts 语法的规则。
1 | npm install --save-dev @typescript-eslint/eslint-plugin |
创建配置文件
ESLint 需要一个配置文件来决定对哪些规则进行检查,配置文件的名称一般是 .eslintrc.js
或 .eslintrc.json
。
当运行 ESLint 的时候检查一个文件的时候,它会首先尝试读取该文件的目录下的配置文件,然后再一级一级往上查找,将所找到的配置合并起来,作为当前被检查文件的配置。
我们在项目的根目录下创建一个 .eslintrc.js
,内容如下:
1 | module.exports = { |
检查一个ts文件
创建完配置文件后,来创建一个ts文件测试eslint是否能检查它,新建一个 index.ts 并输入如下内容
1 | var myName = 'Tom'; |
然后再终端运行 ./node_modules/.bin/eslint index.ts
,会出现如下错误
需要注意的是,我们使用的是 ./node_modules/.bin/eslint
,而不是全局的 eslint
脚本,这是因为代码检查是项目的重要组成部分,所以我们一般会将它安装在当前项目中。
可是每次执行这么长一段脚本颇有不便,我们可以通过在 package.json
中添加一个 script
来创建一个 npm script 来简化这个步骤:
1 | { |
这时只需执行 npm run eslint
即可。
检查整个项目的ts文件
一般我们都会吧代码放在 src
目录下,我们希望检查 src 目录下的所有 ts 文件,所以需要将 package.json
中的 eslint
脚本改为对一个目录进行检查。由于 eslint
默认不会检查 .ts
后缀的文件,所以需要加上参数 --ext .ts
:
1 | { |
此时执行 npm run eslint
即会检查 src
目录下的所有 .ts
后缀的文件。
在 VSCode 中集成 ESLint 检查
在编辑器中集成 ESLint 检查,可以在开发过程中就发现错误,甚至可以在保存时自动修复错误,极大的增加了开发效率。
要在 VSCode 中集成 ESLint 检查,我们需要先安装 ESLint 插件,点击「扩展」按钮,搜索 ESLint,然后安装即可。
VSCode 中的 ESLint 插件默认是不会检查 .ts
后缀的,需要在「文件 => 首选项 => 设置 => 工作区」中(也可以在项目根目录下创建一个配置文件 .vscode/settings.json
),添加以下配置:
1 | { |
这时再打开一个 .ts
文件,将鼠标移到红色提示处,即可看到这样的报错信息了
我们还可以开启保存时自动修复的功能,通过配置:
1 | { |
就可以在保存文件后,自动修复为:
1 | let myName = 'Tom'; |
使用 Prettier 修复格式错误
ESLint 包含了一些代码格式的检查,比如空格、分号等。但前端社区中有一个更先进的工具可以用来格式化代码,那就是 Prettier。
Prettier 聚焦于代码的格式化,通过语法分析,重新整理代码的格式,让所有人的代码都保持同样的风格。
首先需要安装 Prettier:
1 | npm install --save-dev prettier |
然后创建一个 prettier.config.js
文件,里面包含 Prettier 的配置项。Prettier 的配置项很少,这里我推荐大家一个配置规则,作为参考:
1 | // prettier.config.js or .prettierrc.js |
使用 AlloyTeam 的 ESLint 配置
ESLint 原生的规则和 @typescript-eslint/eslint-plugin
的规则太多了,而且原生的规则有一些在 TypeScript 中支持的不好,需要禁用掉。
这里我推荐使用 AlloyTeam ESLint 规则中的 TypeScript 版本,它已经为我们提供了一套完善的配置规则,并且与 Prettier 是完全兼容的(eslint-config-alloy 不包含任何代码格式的规则,代码格式的问题交给更专业的 Prettier 去处理)。
安装:
1 | npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy |
在你的项目根目录下创建 .eslintrc.js
,并将以下内容复制到文件中即可:
1 | module.exports = { |
更多的使用方法,请参考 AlloyTeam ESLint 规则
使用 ESLint 检查 tsx 文件
安装 eslint-plugin-react
1 | npm install --save-dev eslint-plugin-react |
package.json 中的 scripts.eslint 添加 .tsx
后缀
1 | { |
VSCode 的配置中新增 typescriptreact 检查
1 | { |