搭建 TypeScript 环境 & TSC 命令的使用 & 配置 tsconfig 文件

一、全局配置 TypeScript 环境

在学习 TS 之前,你需要先配置 TypeScript 环境:

1、查看 TS 版本

首先,需要检查电脑是否安装了 TS:

tsc -v

若是已经安装了,通过此命令就能看到已装 ts 的版本。

2、全局安装 TS

npm i -g typescript

然后,查看 ts 的版本,若是能看到 ts 的版本就说明安装成功了。

 

二、TSC 命令的使用

在使用 tsc 命令时,只有其后不加任何参数,才会使用 tsconfig.json 配置进行编译检查。

tsc

此时编译器会从当前目录开始查找 tsconfig.json 文件,如果当前目录没有发现该文件,则逐级向父级目录搜索。如果一直没有检索到该文件,编译器会给出使用提示。

TSC 的可选项:

  • –project(简写 -p):编译器直接在该目录下查找 tsconfig.json 文件,如果没找到则报错。
  • –build(简写 -b):编译器会进行增量构建。先找到所有引用的工程,然后检查它们是否为最新版本,最后按顺序构建非最新版本的工程。
  • –verbose(简写 -v):打印详细的日志(可以与其它标记一起使用)。
  • –dry: 显示将要执行的操作但是并不真正进行这些操作。
  • –clean: 删除指定工程的输出(可以与–dry 一起使用)。
  • –force: 把所有工程当作非最新版本对待。
  • –watch(简写 -w):观察模式。watch 模式监控当前项目 ts 文件变化立即进行编译。
  • 其他 TSC 的可选项请参见:TS 中文官网配置文件之编译选项

1、使用 tsc 生成 tsconfig.json 配置文件

在项目中,一般会在根目录下生成唯一一个 tsconfig.json 配置文件。

在指定目录下执行:

tsc --init

就会在该目录下生成一个 tsconfig.json 配置文件,该配置文件中只有 compilerOptions 的配置。其默认配置项请参见本文的 tsconfig 配置文件的解析–> 顶层属性–>compilerOptions 部分。

2、通过 tsc 编译指定的 ts 文件

首先,进入到该文件所在的目录下。

然后,将该 TS 文件通过以下命令编译为 JS 文件:

tsc <文件名.ts>

3、通过 tsc 自动编译 ts 文件

首先,在 tsconfig.json 配置文件中,修改以下配置:

"outDir": "./js", // 为所有发出的文件指定一个输出文件夹
"strict": false, // 是否启用所有严格类型检查选项

然后,我们要通过 watch 开启 TS 监听模式,让其自动编译:

tsc --watch // 可简写为 -w

 

三、tsconfig.json 配置文件的解析

1、顶层属性

(1)、compilerOptions

compilerOptions 是编译选项,可以被忽略,这时编译器会使用默认值。

用 tsc --init 生成的 tsconfig.json 配置文件的默认配置只包含 compilerOptions,具体如下(我在里面加了一些额外的注释):

{
  "compilerOptions": {
    /*请访问 https://aka.ms/tsconfig.json 阅读有关此文件的更多信息*/

    /*项目*/
    // "incremental": true,                              /*启用增量编译——只编译修改过的文件,这个时候会生成tsconfig.tsbuildinfo,下次编译的时候会进行对比只编译修改过的文件*/
    // "composite": true,                                /*启用允许TypeScript项目与项目引用一起使用的约束——指定文件用来存储增量编译信息,默认是tsconfig.tsbuildinfo*/
    // "tsBuildInfoFile": "./",                          /*指定的文件夹。tsbuildinfo增量编译文件——指定文件用来存储增量编译信息,默认是tsconfig.tsbuildinfo*/
    // "disableSourceOfProjectReferenceRedirect": true,  /*在引用复合项目时禁用首选源文件而不是声明文件*/
    // "disableSolutionSearching": true,                 /*编辑时从多项目引用检查中选择一个项目*/
    // "disableReferencedProjectLoad": true,             /*减少TypeScript自动加载的项目数*/

    /*语言与环境*/
    "target": "es2016",                                  /*为发出的JavaScript设置JavaScript语言版本,并包含兼容的库声明——指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'*/
    /* 注意:如果未指定--lib,则会注入默认的librares列表。注入的默认库为:
       对于 --target ES5: DOM,ES5,ScriptHost
       对于 --target ES6: DOM,ES6,DOM.Iterable,ScriptHost
       TS 绝不会在您的代码中注入polyfill,所以需要你自己制定编译lib */
    // "lib": [],                                        /*指定一组描述目标运行时环境的绑定库声明文件*/
    // "jsx": "preserve",                                /*指定生成的jsx代码——指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'*/
    // "experimentalDecorators": true,                   /*为TC39第2阶段草稿装饰器启用实验支持——用于指定是否启用实验性的装饰器特性*/
    // "emitDecoratorMetadata": true,                    /*为源文件中的修饰声明发出设计类型元数据——用于指定是否为装上去提供元数据支持,关于元数据,也是ES6的新标准,可以通过Reflect提供的静态方法获取元数据,如果需要使用Reflect的一些方法,需要引用ES2015.Reflect这个库*/
    // "jsxFactory": "",                                 /*指定在针对React JSX emit时使用的JSX工厂函数,例如“React”。createElement'或'h'*/
    // "jsxFragmentFactory": "",                         /*指定在以React JSX emit为目标时用于片段的JSX片段引用,例如“React”。“碎片”或“碎片”*/
    // "jsxImportSource": "",                            /*指定在使用`JSX:react JSX*`时用于导入JSX工厂函数的模块说明符*/
    // "reactNamespace": "",                             /*指定为“createElement”调用的对象。这仅适用于目标为'react`JSX emit的情况*/
    // "noLib": true,                                    /*禁用包含任何库文件,包括默认库。d、 ts*/
    // "useDefineForClassFields": true,                  /*发出符合ECMAScript标准的类字段*/

    /*模块*/
    "module": "commonjs",                                /*指定生成的模块代码——指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'*/
    // "rootDir": "./",                                  /*指定源文件中的根文件夹——《后面单独介绍》*/
    // "moduleResolution": "node",                       /*指定TypeScript如何从给定的模块说明符中查找文件——用于选择模块解析策略,有'node'和'classic'两种类型*/
    // "baseUrl": "./",                                  /*指定用于解析非相对模块名称的基本目录——《后面单独介绍》*/
    // "paths": {},                                      /*指定将导入重新映射到其他查找位置的一组条目——《后面单独介绍》*/
    // "rootDirs": [],                                   /*允许在解析模块时将多个文件夹视为一个文件夹——《后面单独介绍》*/
    // "typeRoots": [],                                  /*指定多个类似于`xxx的文件夹/节点_modules/@types`——typeRoots用来指定声明文件或文件夹的路径列表,如果指定了此项,则只有在这里列出的声明文件才会被加载*/
    // "types": [],                                      /*指定要包含的类型包名称,而不在源文件中引用——types用来指定需要包含的模块,只有在这里列出的模块的声明文件才会被加载进来*/
    // "allowUmdGlobalAccess": true,                     /*允许从模块访问UMD全局——不把符号链接解析为真实路径,具体可以了解下webpack和node.js的symlink相关知识*/
    // "resolveJsonModule": true,                        /*启用导入。json文件*/
    // "noResolve": true,                                /*禁止`import`s、`require`s或`reference>`s扩展TypeScript应添加到项目中的文件数*/

    /*JavaScript支持*/
    // "allowJs": true,                                  /*允许JavaScript文件成为程序的一部分。使用“checkJS”选项从这些文件中获取错误——允许编译JS*/
    /* 是否检测JS的语法,例如下面的语法编辑器会报错
       let name = 'paul';
       console.log(name.a.b) */
    // "checkJs": true,                                  /*在已检查类型的JavaScript文件中启用错误报告*/
    // "maxNodeModuleJsDepth": 1,                        /*指定用于检查来自“node_模块”的JavaScript文件的最大文件夹深度。仅适用于“allowJs”*/

    /*散发*/
    // "declaration": true,                              /*生成。d、 ts项目中的TypeScript和JavaScript文件中的文件——如果设为true,编译每个ts文件之后会生成一个js文件和一个声明文件,declaration和allowJs不能同时设为true*/
    // "declarationMap": true,                           /*为d.ts文件创建源映射——值为true或false,指定是否为声明文件.d.ts生成map文件*/
    // "emitDeclarationOnly": true,                      /*仅输出d.ts文件,不输出JavaScript文件*/
    // "sourceMap": true,                                /*为发出的JavaScript文件创建源映射文件——用来指定编译时是否生成.map文件*/
    // "outFile": "./",                                  /*指定一个文件,将所有输出捆绑到一个JavaScript文件中。如果'declaration'为true,则还指定一个捆绑所有文件的文件。d、 ts输出——当module设置为 'amd' and 'system'的时候可以使用此命令,这样可以将ts文件打包到一个目录下*/
    // "outDir": "./",                                   /*为所有发出的文件指定一个输出文件夹——编译后的文件存到到哪个目录下,默认是每一个ts文件的当前目录,,下面的配置就是把ts编译到build目录下*/
    // "removeComments": true,                           /*禁用发送注释——编译的时候删除注释*/
    // "noEmit": true,                                   /*禁用从编译发出文件——不生成编译文件,这个一般比较少用,这个build目录下将没有任何文件,但是会进行编译,有错误会抛出*/
    // "importHelpers": true,                            /*允许每个项目从tslib导入一次助手函数,而不是每个文件包含它们——是否引入npm包tslib中的辅助函数,__extends等*/
    // "importsNotUsedAsValues": "remove",               /*为仅用于类型的导入指定发出/检查行为*/
    // "downlevelIteration": true,                       /*为迭代发出更兼容、但冗长且性能较差的JavaScript——当target为'ES5' or 'ES3'时,为'for-of', spread, and destructuring'中的迭代器提供完全支持*/
    // "sourceRoot": "",                                 /*指定调试器查找参考源代码的根路径——sourceRoot用于指定调试器应该找到TypeScript文件而不是源文件的位置,这个值会被写进.map文件里*/
    // "mapRoot": "",                                    /*指定调试器应在其中定位映射文件的位置,而不是生成的位置——mapRoot用于指定调试器找到映射文件而非生成文件的位置,指定map文件的根路径,该选项会影响.map文件中的sources属性*/
    // "inlineSourceMap": true,                          /*在发出的JavaScript中包含sourcemap文件——inlineSourceMap指定是否将map文件内容和js文件编译在一个同一个js文件中,如果设为true,则map的内容会以//#soureMappingURL=开头,然后接base64字符串的形式插入在js文件底部*/
    // "inlineSources": true,                            /*在发出的JavaScript中的sourcemaps中包含源代码——inlineSources用于指定是否进一步将ts文件的内容也包含到输出文件中*/
    // "emitBOM": true,                                  /*在输出文件的开头发出UTF-8字节顺序标记(BOM)*/
    // "newLine": "crlf",                                /*设置用于发送文件的换行符*/
    // "stripInternal": true,                            /*禁用在JSDoc注释中包含“@internal”的声明*/
    // "noEmitHelpers": true,                            /*禁用在编译输出中生成像`u extends`这样的自定义帮助函数*/
    // "noEmitOnError": true,                            /*如果报告任何类型检查错误,则禁用发送文件*/
    // "preserveConstEnums": true,                       /*禁用删除生成代码中的`const enum`声明*/
    // "declarationDir": "./",                           /*指定生成的声明文件的输出目录*/
    // "preserveValueImports": true,                     /*在JavaScript输出中保留未使用的导入值,否则将被删除*/

    /*互操作约束*/
    // "isolatedModules": true,                          /*确保每个文件都可以安全传输,而不依赖其他导入——isolatedModules的值为true或false,指定是否将每个文件作为单独的模块,默认为true,它不可以和declaration同时设定*/
    // "allowSyntheticDefaultImports": true,             /*当模块没有默认导出时,允许“从y导入x”——用来指定允许从没有默认导出的模块中默认导入*/
    "esModuleInterop": true,                             /*发出额外的JavaScript,以简化对导入CommonJS模块的支持。这将启用“allowSyntheticDefaultImports”以实现类型兼容性——通过为导入内容创建命名空间,实现CommonJS和ES模块之间的互操作性*/
    // "preserveSymlinks": true,                         /*禁用将符号链接解析到其realpath。这与节点中的同一标志相关——不把符号链接解析为真实路径,具体可以了解下webpack和node.js的symlink相关知识*/
    "forceConsistentCasingInFileNames": true,            /*确保导入中的大小写正确*/

    /*类型检查*/
    "strict": true,                                      /*启用所有严格类型检查选项——严格模式将会打开下面的几个选项*/
    /* 不允许变量或函数参数具有隐式any类型,例如
       function(name) {
           return name;
       } */
    // "noImplicitAny": true,                            /*为隐含的'any'类型的表达式和声明启用错误报告*/
    // "strictNullChecks": true,                         /*在进行类型检查时,请考虑'null'和'undefined'——null类型检测,const teacher: string = null;会报错*/
    // "strictFunctionTypes": true,                      /*分配函数时,请检查以确保参数和返回值与子类型兼容——对函数参数进行严格逆变比较*/
    // "strictBindCallApply": true,                      /*检查'bind'、'call'和'apply'方法的参数是否与原始函数匹配——严格检查bind call apply*/
    // "strictPropertyInitialization": true,             /*检查构造函数中声明但未设置的类属性——此规则将验证构造函数内部初始化前后已定义的属性。*/
    // "noImplicitThis": true,                           /*当'this'被赋予'any'类型时,启用错误报告——检测this是否隐式指定*/
    // "useUnknownInCatchVariables": true,               /*将catch子句变量键入“unknown”而不是“any”*/
    // "alwaysStrict": true,                             /*确保始终发出“use strict”——使用js的严格模式,在每一个文件上部声明 use strict*/
    // "noUnusedLocals": true,                           /*在未读取局部变量时启用错误报告——默认false,是否检测定义了但是没使用的变量*/
    // "noUnusedParameters": true,                       /*在未读取函数参数时引发错误——用于检查是否有在函数体中没有使用的参数*/
    // "exactOptionalPropertyTypes": true,               /*将可选属性类型解释为书面形式,而不是添加“undefined”*/
    // "noImplicitReturns": true,                        /*为未在函数中显式返回的代码路径启用错误报告——用于检查函数是否有返回值,设为true后,如果函数没有返回值则会提示*/
    // "noFallthroughCasesInSwitch": true,               /*在switch语句中启用故障案例的错误报告——用于检查switch中是否有case没有使用break跳出switch*/
    // "noUncheckedIndexedAccess": true,                 /*在索引签名结果中包含“undefined”*/
    // "noImplicitOverride": true,                       /*确保使用覆盖修饰符标记派生类中的覆盖成员*/
    // "noPropertyAccessFromIndexSignature": true,       /*对使用索引类型声明的键强制使用索引访问器*/
    // "allowUnusedLabels": true,                        /*禁用未使用标签的错误报告*/
    // "allowUnreachableCode": true,                     /*禁用无法访问代码的错误报告*/

    /*完整性*/
    // "skipDefaultLibCheck": true,                      /*跳过类型检查。d、 ts包含在TypeScript中的文件*/
    "skipLibCheck": true                                 /*跳过类型检查全部。d、 ts文件*/
  }
}

更多 compilerOptions 的配置详见:TS 中文官网配置文件之编译选项

(2)、file

file 数组类型。它表示由 ts 管理的 文件 的具体路径,可以是相对或绝对路径。

这些文件内部有依赖的模块 (或者引入了哪些模块),编译器也会搜索到依赖模块进行编译。

如果某些模块并没有在项目中引入,虽然在项目目录中也不会被编译。

如果不指定 files ,项目目录下的所有文件都会被编译器编译。

{
  // 指定需要编译文件 否则默认当前目录下除了exclude之外的所有.ts, .d.ts,.tsx 文件
   "files": ["./src/**/*"]
}

【注意】files 中不支持 glob 匹配模式的路径。

(3)、 include 和 exclude

include 和 exclude 都是数组类型。

include 用于表示 ts 管理的文件。

exclude 用于表示 ts 排除的文件(即不被编译的文件)。

文件列表可以使用 glob 匹配模式列表,支持的 glob 通配符有:

  • *:匹配 0 或多个字符(不包括目录分隔符)。
  • ?:匹配一个任意字符(不包括目录分隔符)。
  • **/:递归匹配任意子目录。
{
  // 指定需要编译文件 否则默认当前目录下除了exclude之外的所有.ts, .d.ts,.tsx 文件
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "types/**/*.d.ts",
    "types/**/*.ts",
    "build/**/*.ts",
    "build/**/*.d.ts",
    "vite.config.ts"
  ],
  // 不编译某些文件
  "exclude": ["node_modules", "dist", "**/*.js"],
}

【注意】

  • files、exclude 和 include 这三者的优先级是这样的:files > exclude > include 。
  • exclude 默认情况下会排除 node_modulesbower_componentsjspm_packages 和 outDir 目录。

【拓展】
glob:是一个古老的 UNIX 程序,它用来匹配路径文件名(pathname 模式匹配),现在在 Linux Shell 使用和编程方面,glob 还在被广泛使用。glob 的模式匹配跟正则表达式不太一样,它比正则表达式要简单一些。glob 的模式匹配有时也叫做通配符匹配(wildcard matching)。具体的规则可参考:glob 模式匹配简明教程

(4)、compileOnSave

布尔类型,可以让 IDE 在保存文件的时候根据 tsconfig.json 重新生成编译后的文件。

(5)、extends

字符串类型,该值是一个路径,指定另一个配置文件用于继承 tsconfig.json 中的配置。在原文件里的配置最先被加载,原文件里的配置被继承文件里的同名配置所重写。 如果发现循环引用,则会报错。

(6)、typeAcquisition

typeAcquisition 配置项在平时的开发中并不常用,了解即可。

对象类型,设置自动引入库类型定义文件。acquisition 翻译过来是 “获得物、获得” 的意思。在整个项目中,如果存在用 JavaScript 写的库,ts 会自动去 compilerOptions.typeRoots 指定的目录中寻找对应的类型声明文件。这个行为被称为 typeAcquisition (类型获得)。这个行为可以通过 enable 来开启或关闭,且以库级别来指定应用的范围。但我在实践中,通过指定 enable 的值去控制这个行为并未有明显的感官,即使使用 vscode 修改配置后重启也并未生效。

(7)、watchOptions

对象类型,用来配置使用哪种监听策略来跟踪文件和目录。由于 tsc 的监听文件机制依赖于 node 的 fs.watch / fs.watchFile。这两种方法的实现并不相同,前者是采用文件系统的事件做到通知,而后者使用轮询的机制。更多可以查阅 node 官方文档里的:fs.watch 和 fs.watchFile

  • watchFile:字符串类型,配置单个文件的监听策略,必须为一下几个值:

    • useFsEvents (默认):采用系统的文件系统的原生事件机制监听文件更改
    • useFsEventsOnParentDirectory:采用系统的文件系统的原生事件机制监听修改文件所在的目录,这样修改一个文件实际上监听的是此文件所在的目录都被监听了,如此整个项目的文件监听器将显著减少,但可能导致监听并不准确。
    • dynamicPriorityPolling:创建一个动态队列去监听文件,修改频率较低的文件将被减少轮询监听的频率。
    • fixedPollingInterval:固定间隔的检查每个文件是否发生变化。
    • priorityPollingInterval:固定间隔的检查每个文件是否发生变化,但使用启发式监听的文件的检查频率要低于非启发式监听的文件。
  • watchDirectory:字符串类型,配置监听目录的策略,必须为以下几个值(和 watchFile 里的对应值的功能一样):

    • useFsEvents (默认)
    • dynamicPriorityPolling
    • fixedPollingInterval
  • fallbackPolling:当采用系统的文件系统中原生事件机制监听文件时,此选项指定本机的文件监听器被耗尽或者不支持本机文件监听器是编译器采用的轮询策略,可以设置为以下几个值(前三个和 watchFile 里的对应值的功能一样):

    • fixedPollingInterval
    • dynamicPriorityPolling
    • priorityPollingInterval
    • synchronousWatchDirectory:禁用对目录的延迟监听。如果有大量的文件更改,比如在 npm install 时 node_modules 目录发生的变化,延迟监听是非常有用的。但总有些不常见的场景需要禁用延迟监听。
  • synchronousWatchDirectory:布尔类型,是否对目录延迟监听。如果配置为 true ,当文件发生修改时同步的调用回调并更新目录监听器。

  • excludeFiles:字符串数组,用于指定不需要被监听变化的文件

  • excludeDirectories:字符串数组,用于指定不需要被监听变化的目录

(8)、reference

reference 是项目引用,它支持将 TypeScript 程序的结构分割成更小的组成部分。

refrence 的作用是将两个项目关联起来作为一个项目开发,当某个项目代码修改后还能单独编译相应的项目而不是整个项目,实现了关联项目间的懒编译。

下面举个应用场景:

假设:我们要开发一个类似于 lodash 的工具库,并在项目中使用,而且后期很有可能还要在业界推广。为了保证这个工具的顺利开发及推广,我们必须要做相应的单元测试。那这个工具库可以看做一个项目,对其中的每个功能的测试也可作为一个独立的项目。但整个过程中,工具库的开发和测试应该是属于同一个项目下 “分项目” 的。那这种情况下 reference 就很棒了。首先我们搭一个目录出来:

|---- src/
	|---- index.ts    // 整个工具库的入口
	|---- copyDeep.ts // 其中定义了copyDeep方法
|---- test/
	|---- copyDeep.test.ts // copyDeep的单元测试
|---- package.json
|---- tsconfig.json

在 copyDeep.test.ts 中肯定要引用 src/copyDeep,也就是说 test 的项目是依赖于 src 的。如果 src 中的代码发生了变化,整个工具库项目应该重新编译,而 test 项目不应该再被编译,这本来就是合理的。如果 test 项目中的代码发生了变化,那 test 项目应该被重新编译,而 src 项目不应该再被编译。如何在一个项目中配置而做到分别编译相应的子项目呢?首先最先想到的应该是在 tsconfig.json 文件中引入 include 字段配置,我们先尝试一下下面的配置:

{
    "files": [
        "./src/index.ts"
    ],
    "include": [
        "./test/**/*.test.ts"
    ],
    "compilerOptions": {
        "outDir": "./dist/"
    }
}

我们来分析这样配置的会有哪些问题:

  • 首先,从整个项目层面,确实做到了修改任意文件重新编译的功能。但注意,编译的是全量的 ts 文件。
  • 随着日后项目的增大,在 *.test.ts 文件中引入也将逐渐变大。
  • 修改了 src///*.ts 的内容,test//*.ts 也将作为输出,这是我们不希望看到的。

此时,reference 将解决上述的每一个问题,我们修改项目结构如下:

|---- src/
	|---- index.ts    	// 整个工具库的入口
	|---- copyDeep.ts 	// 其中定义了copyDeep方法
	|---- tsconfig.json // 工具库的编译配置文件
|---- test/
	|---- copyDeep.test.ts 	// copyDeep的单元测试
	|---- tsconfig.json 	// 测试的编译配置文件
|---- package.json
|---- tsconfig.json

并修改为以下配置:

// 根目录下的 /tsconfig.json
{
  	"compilerOptions": {
    	"declaration": true, // 为子项目生成.d.ts声明文件
    	"outDir": "./dist",
  	}
}

// src目录下的 /src/tsconfig.json
{
	"extends": "../tsconfig",
	"compilerOptions": {
		"composite": true // 必须设置为true,表明该文件夹为一个子项目
	}
}

// test目录下的 /src/tsconfig.json
{
	"extends": "../tsconfig",
	"references": [
		{ "path": "../src" } // 表示引用了工具库项目
	]
}

这样配置后,如果 src 项目已经编译完成并且输出了编译后的文件, 那在 test 项目中,实际加载的是 src 项目声明的 .d.ts 文件,而且这个声明文件是对 test 项目可见的。另外,如果开启了 watch 模式,修改了内容只会编译相应的项目而不会全量编译。这会显著的加速类型检查和编译,减少编辑器的内存占用。而且在代码结构层命有了一个很清晰的规划。

更多 reference 的配置详见:TS 中文官网配置文件之项目引用

2、重点配置属性

(1)、strict 模式详解

当 Typescript 严格模式设置为 on 时,它将使用 strict 族下的 严格类型规则 对项目中的所有文件进行代码验证。

strict 的严格类型规则包含以下属性:

规则名称 解释
noImplicitAny 不允许变量或函数参数具有隐式 any 类型。
noImplicitThis 不允许 this 上下文隐式定义。
strictNullChecks 不允许出现 null 或 undefined 的可能性。
strictPropertyInitialization 验证构造函数内部初始化前后已定义的属性。
strictBindCallApply 对 bind, call, apply 更严格的类型检测。
strictFunctionTypes 对函数参数进行严格逆变比较。

1⃣️、noImplicitAny

不允许变量或函数参数具有隐式 any 类型。

例如:

// Typescript严格模式
function getName (name) {
    return name;
}
// 报错:Parameter 'name' implicitly has an 'any' type. ts(7006)

如果确定为 name 为 any, 也必须显式的制定:

// Typescript严格模式
function getName1 (name: any) {
    return name;
}

2⃣️、noImplicitThis

不允许 this 上下文隐式定义。

例如:

// Typescript严格模式
function uppercaseLabel () {
    return this.label.toUpperCase()
}

const config = {
    label: 'foo-config',
    uppercaseLabel
}
config.uppercaseLabel();
// 报错:'this' implicitly has type 'any', because it does not have a type annotation. ts(2683)

你可以这样修改:

// Typescript严格模式
interface MyConfig {
    label: string
    uppercaseLabel: (params: void) => string
}

const config: MyConfig = {
    label: 'foo-config',
    uppercaseLabel () {
      return this.label.toUpperCase()
    }
}

3⃣️、strictNullChecks

不允许出现 null 或 undefined 的可能性。

例如:

// 此规则不允许出现 null 或 undefined 的可能性。请看以下示例:
// Typescript 非严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
}
// Typescript 非严格模式下,这样写不会有任何问题。但是,严格模式下,若发现没有匹配到任何值时就会报错
// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  return article.meta
  //  报错:Object is possibly 'undefined'. ts(2532)
}
// 于是你会将改成以下模样:
// Typescript严格模式
function getArticleById (articles: Article[], id: string) {
  const article = articles.find(article => article.id === id)
  if (typeof article === 'undefined') {
    throw new Error(`Could not find an article with id: ${id}.`)
  }

  return article.meta
}

4⃣️、strictPropertyInitialization

验证构造函数内部初始化前后已定义的属性。

例如:

// 此规则将验证构造函数内部初始化前后已定义的属性。
// 必须要确保每个实例的属性都有初始值,可以在构造函数里或者属性定义时赋值
// Property 'username' has no initializer and is not definitely assigned in the constructor.
class User {
    private username: string;
}
  
const user = new User();
  
const username = user.username.toLowerCase();

// 第一种修改方式为username指定类型为string或者undefined
class User {
    private username: string | undefined;
}
  
const user = new User();
// 第二种方式是创建实例的时候初始化值
class User {
   constructor (public username: string) {}
}
// 第三种方式是使用断言明确告诉TS我知道自己在干嘛
const user = new User('paul');
    class User {
    username! : string;
}
const user = new User();
const username = user.username.toLowerCase();

5⃣️、strictBindCallApply

对 bind, call, apply 更严格的类型检测。

例如:

/* 此函数在TS中会报错,Argument of type '[number, number, number]' is not
assignable to parameter of type '[number, number]'.
Types of property 'length' are incompatible */
function sum (num1: number, num2: number) {
    return num1 + num2
}
sum.apply(null, [1, 2, 3]);
// 可以使用...运算符和reduce函数修改
function sum(...args: number[]) {
    return args.reduce<number>((total, num) => total + num, 0)
}
  
sum.apply(null, [1, 2, 3])

6⃣️、strictFunctionTypes

对函数参数进行严格逆变比较。

例如:

// 该规则将检查并限制函数类型参数是逆变而非双变,因为对于逆变类型的接口,TS是允许双变的
declare let f1: (x: Animal) => void;
declare let f2: (x: Dog) => void;
declare let f3: (x: Cat) => void;
f1 = f2;  // 启用 --strictFunctionTypes 时错误   
f2 = f1;  // 正确
f2 = f3;  // 错误

interface Animal {
    Eat(): void
}

interface Dog extends Animal{
    Bark():void
}

interface Cat extends Animal{
    Meow():void
}

interface Comparer<T> {
    compare: (a: T, b: T) => number;
}

declare let animalComparer: Comparer<Animal>;
declare let dogComparer: Comparer<Dog>;

animalComparer = dogComparer;  // 启用 --strictFunctionTypes 时错误
dogComparer = animalComparer;  // 正确

(2)、模块解析 baseUrl

// 假设我们路径如下,此时我们在test.ts中引用 import test2 from '../test2',
-- src
version1
    test.ts
version2
    demo.ts
test2.ts
// 如果我们设置"baseUrl": "./src" 那么我们在ts中引入test2可以写为
import test2 from 'test2',需要注意的是只有我们引用的是绝对路径的时候才会使用baseUrl去解析文件

(3)、路径映射(path)

// 如果我们tsconfig使用如下配置
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "*": [
        "*",
        "version2/*"
      ]
    }
}
// 我们的项目目录如下,此时我们在test.ts中引用 import test2 from 'test2',
// 首先匹配 baseUrl/test2如果找到则停止否则开始寻找 baseUrl/version2/test2
-- src
version1
    test.ts
version2
    demo.ts
test2.ts

(4)、rootDir

假设我的目录结构如下:

-- src
    version1
        test.ts
    version2
        demo.ts

如果我们设置 "rootDir": "./src",那么我们的编译后的文件结构如下:

--build
    version1
        test.ts
    version2
        demo.ts

【注意】项目中除了 src 目录之外的其他地方不能有 ts 文件。

(5)、虚拟目录 rootDirs

// 如果我们tsconfig使用如下配置, 这个时候我们生成了一个虚拟的根目录,这个根目录下存放了version2,version3目录下文件
{
  "compilerOptions": {
    "rootDirs": [
      "src/version2",
      "src/version3",
    ],
}

// 我们的项目目录如下,此时我们在test.ts中引用demo就可以这样使用了 import demo from './demo',
-- src
version1
    test.ts
version2
    demo.ts
test2.ts

 

四、配置 tsconfig 文件时常见的错误信息列表

参见:TS 中文官网配置文件之错误信息列表

 

五、ts 配置案例

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "useDefineForClassFields": true,
    "isolatedModules": true,
    "moduleResolution": "node",
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "strictFunctionTypes": false,
    "jsx": "preserve",
    "allowJs": true,
    "sourceMap": true,
    "esModuleInterop": true,
    "resolveJsonModule": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "experimentalDecorators": true,
    "lib": ["dom", "esnext"],
    "types": ["vite/client"],
    "typeRoots": ["./node_modules/@types/", "./types"],
    "noImplicitAny": false,
    "skipLibCheck": true,
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.d.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "types/**/*.d.ts",
    "types/**/*.ts",
    "build/**/*.ts",
    "build/**/*.d.ts",
    "vite.config.ts"
  ],
  "exclude": ["node_modules", "dist", "**/*.js"]
}




【推荐阅读】
TypeScript 语法
vite 官网 - 功能 - TypeScript 编译器选项

【参考文献】
TypeScript 中文官网的项目配置
typeScript tsconfig 配置详解
tsconfig.json 配置详解
Typescript 严格模式有多严格?
tsconfig.json 文件各字段吐血整理

posted @ 2024-07-01 14:15  CharyGao  阅读(250)  评论(0编辑  收藏  举报