🥪 TypeScript快速入门
1. 导言
1.1 为什么要使用TypeScript
平时在使用js的时候是运行时错误,只有在运行时候js才知道自己的类型是什么。但是有了TypeScript
,他会在我们编译的时候就会提示出错误。还有就是可以帮助我们统一代码规范、风格和质量。
2. TS演练场
不需要我们安装任何环境,可以直接在演练场中测试代码。并直观的看到编译后的代码以及对应的声明文件。
3. 快速入门
3.1 安装与运行
- 建议全局安装
typescript
1 | npm i -g typescript |
- 查看安装是否成功
1 | tsc --version |
- 编译ts文件
1 | tsc 文件路径 |
到此为止,我们已经可以进行文件的编译了,但是会不会有点麻烦呢。小编现在是在node环境中进行编写代码的,我发现当我写完ts代码的时候,进行tsc命令编译,将代码生成到对应的目录,我在运行最后的js文件。好麻烦啊,运行一个代码就要经历这么多的步骤。不要急,这里推荐一个npm
包。
- 建议全局安装
ts-node
1 | npm i -g ts-node |
ts-node
编译并运行文件
ts-node会帮助我们在内存中编译并运行文件,所以在执行ts-node
命令的时候,并不会替我们生成编译后的js
文件。
1 | ts-node 路径 |
nodemon
Nodemon 是一种工具,当检测到目录中的文件更改时,它会自动重新启动 Node 应用程序,从而帮助开发基于Node.js的应用程序。
1 | npm i nodemon -D |
3.2 配置文件
快速生成配置文件
tsc --init
include
指定需要编译的文件
指定需要编译的ts的文件或者目录,默认是匹配我们根目录下面的**/*
,**/
代表的是匹配递归到任意子目录,*
代表的是匹配零个或多个字符(不包括目录分隔符)。我们一般会写成下面这个样子:
1 | { |
outDir
编译文件的输出目录
将ts文件变成js文件之后,js文件的存储位置。
1 | { |
-
exclude
排除编译文件
和include
相对,该配置项是要指定需要排除编译的文件,默认值有node_modules、bower_components、jspm_packages、outDir配置的对应目录
-
target
指定编译生成的版本;lib
类型库
target
是指定生成js的版本,更改target也会更改lib的默认值。而lib
是Ts需要引用的库,即声明文件。
1 | { |
strict
严格模式,开启严格的类型检查
开启严格类型检查之后,一下的配置会跟随strict:true
开启
alwaysStrict
-在代码中注入use strict
,ESM模块化默认就是严格模式,commonJs模块化才会生成nolmplictAny
-不允许隐式的any
nolmplicitThis
-不允许this
有隐式的any
类型strictBindCallApply
-严格的bind\call\apply
检查strictFunctionTypes
-不允许函数参数双向协变strictNullChecks
-不允许把null、undefined
赋值给其他类型的变量strictPropertyInitalization
-类的实例属性必须初始化useUnkownInCatchVariables
-默认catch
子句变量为unknow
,而不是any
如果tsconfig.json
配置文件存在,则在执行tsc
命令的时候,不需要在后面添加文件路径了。
3.3 TS常见类型
3.4 any类型
any可以被赋予任何类型的值,直接给变量赋值null
或undefined
,默认类型是any。
3.5 字面量类型
1 | const v1 = "hello" |
- 如果进行类型推导,直接给变量赋值
null
或undefined
,默认类型是any。 - 因为
const
声明的变量不能更改,所以默认的类型就是常量字面量类型。
3.6 联合类型
1 | // 着三种类型都可以 |
3.7 数组类型
数组类型可以有两种表示方式:类型[]、Array<类型>
1 | const arr1:string[] = ['h', 'e', 'l', 'l', 'o'] |
3.8 元组类型
将数组中的每一项,都规定类型
1 | const tuple1: [string, number] = ["a", 1] |
3.9 函数相关
3.9.1 函数参数和返回值
如果不需要返回值,则填写void
1 | function add(a: number, b: number): string { |
3.9.2 可选参数
可以在某些参数后面加上?
,表示参数是非必需传递的。
1 | function sum(a: number, b: number, c?:number):number { |
可选参数必须要在所有必选参数后面
3.9.3 默认参数
默认参数本身就是可选参数
1 | function sum(a: number, b: number, c = 10) { |
3.9.4 剩余参数
1 | const fn = (a:number, b: number, ...args: number[]) => { |
3.9.5 泛型
1 | // 不确定是什么类型,需要传递一个类型过来 |
3.10 对象字面量类型
对象字面量类型就是字面量类型
1 | const v4 = { |
3.11 自定义类型
3.11.1 类型别名
创建一个类型的新的名字,类型别名可以是任何有效的类型
1 | // type 名称 = 类型 |
3.11.2 接口
接口其实是面向对象的概念,所以一般用于定义对象类型
1 | interface Point { |
3.12 交叉类型
交叉类型就是将多个类型合并成一个类型。类型A & 类型B、 类型A | 类型B
1 | type A = { |
3.13 类型断言
简单来说,TS会根据上下文进行推测,但是有时候我们可以认为干涉,确定某一个类型。类型断言就是告诉TS编译器,我知道我在做什么,这里没有类型安全问题,我自己来掌握,允许我们使用更宽松的方式处理类型问题。
语法:
值 as 类型 或者 <类型>值
1 | let someValue: any = "this is a string" |
3.14非空断言
当你确定某个值不是null或者undefined的时候,可以直接使用非空断言。
语法:值!
1 | let maybeString: string | undefined = "hello" |
3.15 可选链操作符(js语法)
1 | interface Address { |
4. 类型声明
在前面的代码中,我们说从 typescript
编译到 Javascript
的过程中,类型消失了,比如下面的代码:
1 | const str = "hello"; |
编译成javascript之后:
1 | ; |
但是是真的消失了吗?其实并不是,如果大家留意之前我们在Playground上编写代码,专门有一项就叫做DTS
你会发现,我们写的代码都自动转换成了typescript类型声明。
当然,这在我们的VS Code编辑器中也能生成的。只需要在tsconfig.json
文件中加上相关配置即可
1 | { |
运行tsc
,最后生成:[文件名].d.ts
1 | declare const str = "hello"; |
也就是说,类型并不是真的全部消失了,而是被放到了专门的类型声明文件里。
.d.ts
结尾的文件,就是类型声明文件。d
的含义就是declaration
其实typescript
本身就包含两种文件类型
1、.ts
文件:既包含类型信息,又包含可执行代码,可以被编译成.js
文件后执行,主要是我们编写文件代码的地方
2、.d.ts
文件:只包含类型信息的类型声明文件,不会被编译成.js
代码,仅仅提供类型信息,所以类型文件的用途就是提供类型信息
4.1 类型声明文件的来源
类型声明文件主要有以下三种来源。
- TypeScript 编译器自动生成。
- TypeScript 内置类型文件。
- 外部模块的类型声明文件,需要自己安装。
4.1.1自动生成
只要使用编译选项declaration
,编译器就会在编译时自动生成单独的类型声明文件。
下面是在tsconfig.json
文件里面,打开这个选项。
1 | { |
declaration这个属性还与其他属性有强关联:
declarationDir
:指定生成的声明文件d.ts
的输出目录emitDeclarationOnly
:只输出d.ts
文件,不输出 JavaScript 文件declarationMap
:为d.ts
文件创建源映射
4.1.2 内置声明文件
安装 TypeScript 语言时,会同时安装一些内置的类型声明文件,主要是内置的全局对象(JavaScript 语言接口和运行环境 API)的类型声明。这也就是为什么string
,number
等等基础类型,Javascript的api直接就有类型提示的原因
内置声明文件位于 TypeScript 语言安装目录的lib
文件夹内
这些内置声明文件的文件名统一为lib.[description].d.ts的形式,其中description
部分描述了文件内容。比如,lib.dom.d.ts
这个文件就描述了 DOM 结构的类型。
如果想了解对应的全局对象类型接口,可以去查看这些内置声明文件。
tsconfig.json
中的配置target
和lib
其实就和内置声明文件是有关系的。TypeScript 编译器会自动根据编译目标target
的值,加载对应的内置声明文件,默认不需要特别的配置。我们也可以指定加载哪些内置声明文件,自定义配置lib
属性即可:
1 | "lib":["es2020","dom","dom.iterable"] |
为什么我们没有安装typescript之前也有提示?
这是由于我们的
VS Code
等IDE工具在安装或者更新的时候,已经内置了typescript的lib。一般在你的VS Code安装路径
->resources
->app
->extensios
->node_modules
->typescript
下如果你的
VS Code
一直没有升级,就有可能导致本地VS Code
的typescript
版本跟不上的情况,如果你的项目目录下,也安装的的有typescript,我们是可以进行切换的。在
VS Code
中使用快捷键ctrl(command) + shift + P
,输入TypeScript
选择
Select Typescript Version...
你可以选择使用
VS Code
版本还是项目工作区的版本
4.2 外部类型声明文件
如果项目中使用了外部的某个第三方库,那么就需要这个库的类型声明文件。这时又分成三种情况了。
1、第三方库自带了类型声明文件
2、社区制作的类型声明文件
3、没有类型声明文件
没有类型声明这个很容易理解,我们现在不纠结这种情况,而且大多数情况下,我们也不应该去纠结他,关键是1,2两点是什么意思?其实我们下载两个常用的第三方库就能很明显的看出问题。
1 | npm i axios lodash |
注意:引入模块之前,涉及到模块的查找方式,因此在tsconfig.json中需要配置**module**
对于现代 Node.js 项目,我们可以配置
NodeNext
,注意这个配置会影响下面的配置:
1
2 "moduleResolution": "NodeNext",
"esModuleInterop": true当然,具体模块化的配置,不同的环境要求是不一样的,有一定的区别,比如是nodejs环境,还是webpack的打包环境,或者说是在写一个第三方库的环境,对于模块化的要求是不一样的。而且还涉及到模块化解析方式等问题。这里就先不详细深入讲解了
在nodejs环境下,我们先简单配置为
"module":"NodeNext"
在webapck/vite等打包环境下,设置为:
"module": "ESNext"
"moduleResolution": "bundler"
引入相关模块:
其实打开这两个库的源代码就能发现问题,axios是有.d.ts
文件的,而lodash没有,也就是说根本没有类型声明,那当然就和提示的错误一样,无法找到模块的声明文件。
第三方库如果没有提供类型声明文件,社区往往会提供。TypeScript 社区主要使用 DefinitelyTyped,各种类型声明文件都会提交到那里,已经包含了几千个第三方库。上面代码提示的错误,其实就是让我们到@types
名称空间去下载lodash对应的类型声明,如果存在的话。当然,你也可以到npm上进行搜索。几乎你知道的所有较大的库,都会在上面找到,所以一般来说也要下载或者搜索都比较简单,@types
开头,/
后面加上第三方库原来的名字即可,比如:
@types/lodash
,@types/jquery
,@types/node
,@types/react
,@types/react-dom
等等
1 | npm i --save-dev /lodash |
1 | import lodash from 'lodash' |
默认情况下,所有可见的“@types
”包都会包含在你的编译中。任何包含文件夹中node_modules/@types
的包都被视为可见。“任何包含文件夹”意味着不仅是项目的直接node_modules/@types
目录会被搜索,上层目录中的相应文件夹也会被递归搜索。
可以通过typeRoots选项设置查找的文件路径,如果指定了typeRoots
,则只会包含typeRoots
下的包。例如:
1 | { |
这个配置文件将会包含./typings
和./vendor/types
下的所有包,但不会包含./node_modules/@types
下的任何包。所有路径都是相对于tsconfig.json
文件的。
也就是说,如果你要手动指定typeRoots
,那就需要自己手动指定所有需要查找的目录,如果你的项目中有深层次的目录结构,并且你希望包含其中的类型声明,你需要确保这些目录都被明确地添加到typeRoots
中。
其实,nodejs本身也没有TypeScript的类型声明,因此你会发现在.ts
文件中直接引入nodejs相关的模块同样会报错
1 | import path from "path"; // error 找不到模块"path"或其相应的类型声明 |
同样,我们直接在DefinitelyTyped下载即可
1 | npm i /node -D |
5. 类型声明文件的用途
我们自己当然也能编写类型声明文件,但是声明文件.d.ts
大多数时候是第三方库一起使用的,我们写代码在nodejs环境下,单独去声明.d.ts
文件没有太大的意义,首先大家要知道这个问题。所以,要使用.d.ts
声明文件的场景一般是:
1、自己写了一个主要是Javascript代码的第三方库,需要给这写Javascript代码加上类型声明,以便用户使用的时候可以得到类型声明,方便调用API。
2、自己下载了别人写的第三方库,但是没有typescript类型声明,在社区 DefinitelyTyped中也没有找到对应的类型声明,但是我们一定要用这个库,可以手动为这个库添加一些简单的类型声明,以免我们自己项目在使用这个第三方库没有类型声明报出错误提示。
3、在做应用项目的时候,需要补充一些全局的类型声明的时候,我们可能需要自己动手写.d.ts
文件,其实这种情况大多数还是和第2点有关系