2-1. 安装 TypeScript

前言

任务列表

  • 理解为何选择 node 作为宿主环境来介绍 ts,而不是选择浏览器环境
  • 了解 ts 的默认行为(默认配置)
  • 了解改变 ts 默认行为的两种方式

notes

默认配置

其实在 1-3 的结尾,我们就已经完成了 node 环境下 ts 开发环境的搭建。就是全局安装 typescript:npm i -g typescript

但是,这样搭建的开发环境是最简单的,我们还要学习 typescript 环境的相关配置。

在正式介绍配置的相关知识点之前,先对 1-3 中,ts 文件编译后的一些怪异行为做一个简单的说明。

这些怪异行为包括:

  1. 为什么编译后,原来的 ts 文件就报错;
  2. 为什么编译后生成的 js 文件中的代码和原始 ts 文件中的代码不同;

在 1-3 中,我们执行命令 tsc index.ts,将 index.ts 文件给编译为了 index.js 文件。

image.png

此时,我们并没有配置任何东西,使用的就是默认配置,在默认配置下:

  1. 假设当前的环境是 dom 环境;
    由于默认环境是 dom 环境,所以,我们输入 windowdocument 这样的关键字,它都能识别出来。
  2. 如果代码中没有使用模块化语句(importexport 等),便认为该代码是全局执行的;
    由于 index.ts 代码中没有模块化语句,所以这些代码都是丢到全局中的。所以,index.ts 文件误认为变量重复声明,就报错了。
  3. 编译后生成的目标代码,会转换为等效的 es3 代码;
    由于编译后生成的是 es3 的代码,所以新生成的 index.js 文件中的等效代码会发生变化。

改变默认配置的方式

有两种方式来改变默认配置:

  1. 使用 tsc 命令行的时候,加上相关的选项参数;(不常用,没尝试过)
  2. 使用 ts 配置文件,配置相关的编译选项;(常用,下节课 2-2 会介绍)

Q & A

🤔 为什么我们选择在 node 环境中搭建 typescript 开发环境,而不是在浏览器中搭建? 我们上一节课程中介绍过,TS 是 JS 的超集,但凡是 JS 可以跑的地方,TS 都能跑。

这也就是说,在浏览器环境下,TS 也是可跑的。

之所以在 node 中搭建,其实主要是考虑到 node 环境简单一些,在 node 环境下,更有助于我们学习 TS 语言本身。

🤔 为什么编译后,原来的 ts 文件会报错? 在执行 tsc 命令进行编译之前,index.ts 文件不会报错;
在执行 tsc 命令进行编译之后,index.ts 文件突然就报错了;

image.png

这是因为 typescript 在默认情况下,认为 index.js 和 index.ts 中的内容都是丢到全局环境中执行的。

由于已经在 index.js 文件中声明了变量 a,所以如果又在 index.ts 文件中使用 let 关键字重复声明变量 a,是会报错的。

image.png

🤔 为什么目标文件 index.ts 中使用的是 let 关键字,而到了编译结果 index.js 中是就变成了 var 关键字? image.png

这是编译选项 compilerOptions.target 的默认值 es3 决定的

  1. {
  2. "compilerOptions": {
  3. "target": "es3"
  4. }
  5. }

编译后生成的目标代码,会转换为等效的 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 命令来生成的文件的默认内容如下:

  1. {
  2. "compilerOptions": {
  3. /* Visit https://aka.ms/tsconfig.json to read more about this file */
  4. /* Projects */
  5. // "incremental": true, /* Enable incremental compilation */
  6. // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
  7. // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
  8. // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
  9. // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
  10. // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
  11. /* Language and Environment */
  12. "target": "es5", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
  13. // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
  14. // "jsx": "preserve", /* Specify what JSX code is generated. */
  15. // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
  16. // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
  17. // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
  18. // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
  19. // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
  20. // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
  21. // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
  22. // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
  23. /* Modules */
  24. "module": "commonjs", /* Specify what module code is generated. */
  25. // "rootDir": "./", /* Specify the root folder within your source files. */
  26. // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
  27. // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
  28. // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
  29. // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
  30. // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
  31. // "types": [], /* Specify type package names to be included without being referenced in a source file. */
  32. // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
  33. // "resolveJsonModule": true, /* Enable importing .json files */
  34. // "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
  35. /* JavaScript Support */
  36. // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
  37. // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
  38. // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
  39. /* Emit */
  40. // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
  41. // "declarationMap": true, /* Create sourcemaps for d.ts files. */
  42. // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
  43. // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
  44. // "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. */
  45. // "outDir": "./", /* Specify an output folder for all emitted files. */
  46. // "removeComments": true, /* Disable emitting comments. */
  47. // "noEmit": true, /* Disable emitting files from a compilation. */
  48. // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
  49. // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
  50. // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
  51. // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
  52. // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
  53. // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
  54. // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
  55. // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
  56. // "newLine": "crlf", /* Set the newline character for emitting files. */
  57. // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
  58. // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
  59. // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
  60. // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
  61. // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
  62. /* Interop Constraints */
  63. // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
  64. // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
  65. "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
  66. // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
  67. "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
  68. /* Type Checking */
  69. "strict": true, /* Enable all strict type-checking options. */
  70. // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
  71. // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
  72. // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
  73. // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
  74. // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
  75. // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
  76. // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
  77. // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
  78. // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
  79. // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
  80. // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
  81. // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
  82. // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
  83. // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
  84. // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
  85. // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
  86. // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
  87. // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
  88. /* Completeness */
  89. // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
  90. "skipLibCheck": true /* Skip type checking all .d.ts files. */
  91. }
  92. }

每一个配置项都有对应的注释,后续的学习过程中,需要用到的配置,会挨个介绍。
下面先介绍一些比较常用的配置项

{
  "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) 来配置

可以这么理解:

  1. 安装 @types/nodenpm i @types/node
  2. 配置编译选项 ,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"
    // 配置编译目标所在的位置
  ]
}

image.png

🤔 为什么按照这种配置,我们没法使用 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 的相关配置,我们已经可以实现:

  1. 编辑 tsconfig.js 文件;(独立完成一些基本编译选项的配置)
  2. 在 src 目录下开开心心地写 ts 代码;
  3. 执行编译命令 tsc;tsc
  4. 在 dist 目录下生成我们需要的结果代码;
  5. 执行 dist 目录下的结果代码;node ./dist/xxx.js

但是,这流程还是比较繁琐的,我们还可以对上述流程进行简化。

主要是简化后边的四步: 2~5,只要编写好两个配置文件 package.json、tsconfig.json 即可。

  1. package.json 文件,用于配置 scripts 脚本,减少我们手写长命令的成本
  2. 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 进行全局安装,报错 - 没权限 image.png

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后的提示信息:

image.png

我们会发现,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后的提示信息:

image.png

虽然通过 -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 不会。(原因不知道,先记住吧)