2-1. 安装 TypeScript
前言
任务列表
- 理解为何选择 node 作为宿主环境来介绍 ts,而不是选择浏览器环境
- 了解 ts 的默认行为(默认配置)
- 了解改变 ts 默认行为的两种方式
notes
默认配置
其实在 1-3 的结尾,我们就已经完成了 node 环境下 ts 开发环境的搭建。就是全局安装 typescript:npm i -g typescript
。
但是,这样搭建的开发环境是最简单的,我们还要学习 typescript 环境的相关配置。
在正式介绍配置的相关知识点之前,先对 1-3 中,ts 文件编译后的一些怪异行为做一个简单的说明。
这些怪异行为包括:
- 为什么编译后,原来的 ts 文件就报错;
- 为什么编译后生成的 js 文件中的代码和原始 ts 文件中的代码不同;
在 1-3 中,我们执行命令 tsc index.ts
,将 index.ts
文件给编译为了 index.js
文件。
此时,我们并没有配置任何东西,使用的就是默认配置,在默认配置下:
- 假设当前的环境是 dom 环境;
由于默认环境是 dom 环境,所以,我们输入window
,document
这样的关键字,它都能识别出来。 - 如果代码中没有使用模块化语句(
import
、export
等),便认为该代码是全局执行的;
由于index.ts
代码中没有模块化语句,所以这些代码都是丢到全局中的。所以,index.ts
文件误认为变量重复声明,就报错了。 - 编译后生成的目标代码,会转换为等效的 es3 代码;
由于编译后生成的是 es3 的代码,所以新生成的index.js
文件中的等效代码会发生变化。
改变默认配置的方式
有两种方式来改变默认配置:
- 使用 tsc 命令行的时候,加上相关的选项参数;(不常用,没尝试过)
- 使用 ts 配置文件,配置相关的编译选项;(常用,下节课 2-2 会介绍)
Q & A
🤔 为什么我们选择在 node 环境中搭建 typescript 开发环境,而不是在浏览器中搭建? 我们上一节课程中介绍过,TS 是 JS 的超集,但凡是 JS 可以跑的地方,TS 都能跑。
这也就是说,在浏览器环境下,TS 也是可跑的。
之所以在 node 中搭建,其实主要是考虑到 node 环境简单一些,在 node 环境下,更有助于我们学习 TS 语言本身。
🤔 为什么编译后,原来的 ts 文件会报错?
在执行 tsc 命令进行编译之前,index.ts
文件不会报错;
在执行 tsc 命令进行编译之后,index.ts
文件突然就报错了;
这是因为 typescript 在默认情况下,认为 index.js 和 index.ts 中的内容都是丢到全局环境中执行的。
由于已经在 index.js 文件中声明了变量 a,所以如果又在 index.ts 文件中使用 let 关键字重复声明变量 a,是会报错的。
🤔 为什么目标文件 index.ts 中使用的是 let 关键字,而到了编译结果 index.js 中是就变成了 var 关键字?
这是编译选项 compilerOptions.target 的默认值 es3
决定的
{
"compilerOptions": {
"target": "es3"
}
}
编译后生成的目标代码,会转换为等效的 es3 代码;
有关编译选项 compilerOptions 的更多字段,会在下一节 2-2 中介绍。
2-2. TypeScript 配置文件
前言
任务列表
- 掌握两种向我们的工程中添加配置文件的方式,并理解这两种方式之间的区别
tsc --init
- 手动创建一个名为
tsconfig.json
的文件
- 添加 node 环境:
npm i -D @types/node
- 配置项
compilerOptions
-
target
-
module
-
lib
-
outDir
- 配置项
include
- 配置项
filename
参考资料
介绍 @types
介绍 typescript 中的类型声明文件
告诉我们应该如何写类型声明文件
暂时还不需要深入了解,后续会有对应的课程(倒数第二个章节)介绍这一部分的内容
notes
添加配置文件
向我们的工程中添加 ts 的配置文件的两种方式:
方式1:在工程的根目录中创建一个 tsconfig.json
文件,该文件就是 ts 的配置文件。
方式2:在命令行,使用 tsc --init
命令来初始化一个 ts 的配置文件。
如果是采用方式1,那么生成的配置文件中将不含任何内容,就是一个普普通通的空文件;
如果是采用方式2,那么生成的配置文件中,还是含有一些内容的;
配置项
使用 tsc --init
命令来生成的文件的默认内容如下:
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Projects */
// "incremental": true, /* Enable incremental compilation */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "resolveJsonModule": true, /* Enable importing .json files */
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
// "outDir": "./", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
每一个配置项都有对应的注释,后续的学习过程中,需要用到的配置,会挨个介绍。
下面先介绍一些比较常用的配置项
{
"compilerOptions": {
"target": "es2016", // default value "es3"
"module": "commonjs",
"lib": ["es2016"], // default value ["dom"]
"outDir": "./dist"
},
"include": ["./src"]
}
compilerOptions
配置的是和编译相关的一些选项;
compilerOptions.target
配置编译结果的版本标准;
compilerOptions.module
配置编译目标使用的模块化标准;
在 node 环境下,通常都是采用 commonjs 模块化规范。
compilerOptions.lib
配置 ts 在默认情况下的运行环境,默认为 dom
它是一个数组,表示我们可以添加很多环境
compilerOptions.outDir
用于告诉编译器,编译生成的结果代码所存放的目录。
include
用于告诉编译器,需要编译的 ts 代码文件的所在目录。
如果我们没有配置指定的目录,那么当我们执行编译命令 tsc
时,它会把整个工程中的所有 ts 代码文件都进行编译。
files
include 是指定编译哪些目录下的 ts 文件,files 是指定具体编译哪些 ts 文件,files 指定更加明确一些。
:::warning
当我们添加了配置文件后,再使用 tsc 命令去编译我们的 .ts 文件时,只需要输入 tsc
即可。
tsc 的后边不需要跟上文件名 tsc index.ts
,若跟上文件名,那么会忽略配置文件,相当于我们没有写配置文件。
:::
@types/node
安装:npm i -D @types/node
:::warning
在 compilerOptions.lib
的可选环境中,没有 node 这个环境可供我们直接选择
若想要添加 node 环境,我们得借助第三方库 @types
(@types/node) 来配置
可以这么理解:
- 安装
@types/node
,npm i @types/node
- 配置编译选项 ,
compilerOptions.lib = ['node']
当我们执行 1 之后,相当于在 lib 中添加了一个 node :::
没有类型检查的第三方库:
有很多第三方库,比如 jQuery、mock、moment、lodash 等等,这些第三方库都是使用 js 写的。
我们知道,js 是没有类型检查的,那么当我们想要在 ts 中使用这些第三方库时,就可以尝试到 @types 中找对应的类型库。
示例:
在 ts 中使用 jQuery 库,我们只要安装 @types/jquery
库就可以为 jQuery 添加类型定义。
添加上 jQuery 的类型定义后,我们在 ts 中写 jQuery 代码时,就可以知道具体的类型。比如:jQuery 中的 $
是什么类型,$ 中的某个方法的参数是什么类型,方法的返回值是什么类型,等等。
@types/node
在了解到上述相关知识后,就不难理解 @types/node
这个库的作用了。我们可以将 node 也看做是一个类似于 jQuery 那样的第三方库,node 也是使用 js 来实现的,又由于 js 中没有类型检查。所以,@types/node
其实就是用来描述 node 环境下各种各样的类型的。(ts 自带浏览器环境下的类型检查)
Q & A
{
"compilerOptions": {
"target": "es2016",
// 配置编译结果的 es 版本为 es2016(也就是 es7)
"module": "commonjs",
// 配置 ts 中所使用的的模块化规范为 commonjs
"lib": [ // 配置当前 ts 的环境库
"es2016"
// "node" // 注意没有 lib 的可选值中,没有 node,如果要添加 node 环境,得通过 @types 库来添加,npm i @types/node
],
"outDir": "./dist"
// 配置编译结果存放的位置
},
"include": [
"./src"
// 配置编译目标所在的位置
]
}
🤔 为什么按照这种配置,我们没法使用 console 对象? 通过提示信息得知,ts 告诉我们,如果想要使用 console 对象,你可以尝试在 compilerOptions.lib 中添加上 “dom”
因为浏览器宿主环境中,含有 console 对象,我们需要告诉 ts 当前环境是浏览器环境。
但是,实际上我们的环境是 node 环境,而非浏览器环境。可是 node 环境下也是可以使用 console 对象的,因为 node 环境中也有 console。不过由于 compilerOptions.lib 中不含 “node” 选项,所以我没没法通过它来配置。
解决方案:npm i -D @types/node
既然没法配置,那就手动添加 node 环境下的类型声明文件。
🤔 如果在使用 tsc 对目标文件进行编译时,后边带上了文件路径,配置文件中的配置是否依旧有效?
# Run a compile based on a backwards look through the fs for a tsconfig.json
tsc
# Emit JS for just the index.ts with the compiler defaults
tsc index.ts
如果直接使用 tsc
,那么会基于 typescript 的配置文件 tsconfig.json 来编译目标文件;
如果在 tsc
命令后边跟上指定的目标文件,将会使用默认的编译选项来编译目标文件;(此时,即便我们写了 ts 的配置文件,它也会忽略)
小结
如果想要配置文件中的配置生效,直接使用 tsc
命令对目标文件进行编译即可,不要多此一举地跟上文件路径。
🤔 当我们使用 tsc 命令的时候,它会编译哪些位置的 ts 文件呢? 在默认情况下,会编译整个工程中的 ts 文件,我们可以通过字段 include 来定义
{
"include": ["./src"] // include 字段,用于告诉 ts 需要编译哪些位置的 ts 文件
}
2-3. 使用第三方库简化流程
前言
任务列表
- 掌握
ts-node
库的基本使用 - 掌握
nodemon
库的基本使用 - 能够独立完成「需求」
需求:
启动工程:npm run dev
1. 我们的所有 ts 代码,都写在 src 目录下边
2. 每当 src 下边的 ts 代码发生变化之后,立刻自动在内存中完成编译,并运行
打包:tsc
将 src 目录下边的 ts 文件,打包到 dist 目录中
参考资料
notes
通过上节课学习的 typescript 的相关配置,我们已经可以实现:
- 编辑 tsconfig.js 文件;(独立完成一些基本编译选项的配置)
- 在 src 目录下开开心心地写 ts 代码;
- 执行编译命令 tsc;
tsc
- 在 dist 目录下生成我们需要的结果代码;
- 执行 dist 目录下的结果代码;
node ./dist/xxx.js
但是,这流程还是比较繁琐的,我们还可以对上述流程进行简化。
主要是简化后边的四步: 2~5,只要编写好两个配置文件 package.json、tsconfig.json 即可。
- package.json 文件,用于配置 scripts 脚本,减少我们手写长命令的成本
- tsconfig.json 文件,用于配置基本的编译选项
具体如何简化,就是本节课要介绍的内容。
ts-node
简介:ts-node 是一个第三方库,它可以将 ts 代码在内存中完成编译,同时会运行编辑的结果(js 文件)。
安装:npm i -g ts-node
使用:有了这个包之后,我们只需要在 src 目录下写好我们的 ts 代码,然后执行 ts-node ./src/xxx.ts
命令即可。
由于是在内存中编译并运行的,所以,它并没有给我们生成目标文件。不过,这点也正是我们在开发阶段希望看到的效果。
在使用该命令时,和 tsc 的不同点在于:
tsc 后边不能跟具体的文件,否则配置文件将失效;
ts-node 后边可以跟具体的文件,并不会忽略 ts 配置文件,tsconfig.js 中的配置依旧是有效的;
nodemon
使用 ts-node 虽然已经简化了流程,但是,我们每次修改 ts 文件后,还要手动输入命令 ts-node ./src/xxx.ts
去编译执行。
如果我们希望 ts 文件内容发生变化后,就会自动编译并运行,那么可以使用 nodemon 这个第三方库。
简介:nodemon,全称应该是 node monitor,node 监视器。开发 express、koa 这样的应用程序的时候,一般都会用到这个库。
全局安装:npm i -g nodemon
记录一个问题:mac 使用 npm 进行全局安装,报错 - 没权限
sudo npm i -g nodemon
这个问题知道该如何解决就好了,之后每次安装,估计都会报错,说没有权限。此时记得在前边加上 sudo 就好。
使用:nodemon --exec ts-node ./src/index.ts
注解:exec 是 execute 的缩写,表示执行的意思。一旦监测(monitor)到我们的资源发生变化,就执行命令 ts-node ./src/index.ts
。 具体需要监测哪些资源,我们是可以通过 nodemon 的配置参数来指定的。
By default, nodemon looks for files with the
.js
,.mjs
,.coffee
,.litcoffee
, and.json
extensions.译:默认情况下,nodemon 会监测以
.js
、.mjs
、.coffee
、.litcoffee
、.json
为后缀的文件
-e
命令参数:-e
ext -> extension 扩展名,后缀名。
执行 nodemon --exec ts-node ./src/index.ts
后的提示信息:
我们会发现,nodemon 它监测的文件,除了 .ts
文件之外,还会监测 .json
文件,监测的范围有点广,若我们只想让它监测 .ts
文件,那么又改如何实现呢?
我们可以在 nodemon 命令后边加上命令参数 --ext
(简写 -e
)来指定具体监测哪些后缀名的文件。
只需要将原来配置的命令稍加修改即可:nodemon -e ts --exec ts-node ./src/index.ts
-e ts
表示监听后缀为 ts 的文件。
—watch
命令参数:—watch
执行 nodemon -e ts --exec ts-node ./src/index.ts
后的提示信息:
虽然通过 -e 配置了指监测 ts 文件的变化,但是,若根目录下存在一个 ts 文件,我们更改了该 ts 文件的内容,会发现 ./src/index.ts
也会被执行一遍。
这是因为我们还没有指定具体监测哪个目录,nodemon 默认会监测当前的工作目录下的所有 ts 文件。
我们可以通过 --watch
这个参数来指定 nodemon 监测的目录。
nodemon --watch src -e ts --exec ts-node ./src/index.ts
--watch src
表示仅监听 src 目录
nodemon --watch src -e ts --exec xxx
监听 src 目录下的所有 .ts 文件内容的变化,一旦文件内容发生变化,那么就执行命令 xxx
codes
{
"compilerOptions": {
"target": "ES2016",
"lib": ["ES2016"],
"outDir": "./dist"
}
}
{
"scripts": {
"dev": "nodemon --watch ./src -e .ts --exec ts-node ./src/index.ts"
},
"devDependencies": {
"@types/node": "^17.0.42"
}
}
命令有点长,我们可以将其配置到 package.json 的 scripts 字段中。
配置好 package.json 之后,我们下次启动时,就只需要执行 npm run dev
命令即可。
至此,我们就搭建了一个简易的 ts 开发环境。
Q & A
🤔 如果使用 ts-node 命令,完成对某个目标文件(ts 文件)的编译,是否会像 tsc 命令那样,在后边加上后缀名,导致忽略 tsconfig.json 配置文件呢? 不会
为什么 tsc 会,而 ts-node 不会。(原因不知道,先记住吧)