TypeScript,是,JavaScript,的,超集,添加了,静态类型,系统。这两年,越来越,火,很多,大公司,都,在用。

用,TypeScript,的,好处,很多。比如,编译时,就能,发现,类型,错误,减少,运行时的,bug。比如,IDE,的,智能提示,更,强大,开发,效率,更高。比如,代码,的,可读性,和,可维护性,更好。

但是,很多人,刚,接触,TypeScript,的时候,都,被,配置,搞晕了。tsconfig.json,里面,一大堆,选项,不知道,什么意思,怎么配。

我,刚,接触,TypeScript,的时候,也是,这样。看着,tsconfig.json,里面的,各种,选项,一头雾水。只能,抄,别人的,配置,或者,用,默认的,配置。

后来,用,多了,才,慢慢,搞懂了,各个,选项,的,意思,和,用法。

今天,想,详细,讲解一下,TypeScript,的,入门配置,从基础,到高级。希望,能,帮,大家,搞懂,TypeScript,的,配置。

一、什么是tsconfig.json?

首先,说说,什么是,tsconfig.json

tsconfig.json,是,TypeScript,的,配置文件。它,告诉,TypeScript,编译器,怎么,编译,你的,项目。

当,你,在,一个,目录,下,运行,tsc,命令,的时候,TypeScript,编译器,会,自动,查找,当前目录,下的,tsconfig.json,文件,按照,里面的,配置,来,编译。

如果,没有,tsconfig.json,TypeScript,会,用,默认的,配置,来,编译。但是,默认配置,往往,不能,满足,项目的,需求。所以,一般,都,需要,自己,创建,tsconfig.json,来,配置。

创建,tsconfig.json,很简单。你,可以,手动,创建,一个,JSON文件,也,可以,用,命令,tsc --init,来,生成,一个,默认的,tsconfig.json

用,tsc --init,生成的,tsconfig.json,里面,有,很多,注释,解释了,每个,选项,的,意思。对于,初学者,来说,很,友好。

二、最基础的配置

先,说说,最基础的,配置。

一个,最简单的,tsconfig.json,可能,长这样:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "strict": true
  },
  "include": ["src/**/*"]
}

这,几个,选项,是,最,常用的,也是,最,基础的。

1. target

target,指定,编译,后的,JavaScript,版本。

可选值,有:es3es5es6(也叫,es2015),es2016es2017es2018,等等。

一般,推荐,用,es5,因为,兼容性,最好,几乎,所有的,浏览器,都,支持。

如果,你的,项目,只,需要,支持,现代浏览器,或者,Node.js,环境,可以,用,更高的,版本,比如,es2017,这样,编译,后的,代码,更,简洁,性能,也,更好。

2. module

module,指定,编译,后的,模块,系统。

可选值,有:nonecommonjsamdsystemumdes6es2015,等等。

如果,你的,项目,是,Node.js,项目,一般,用,commonjs

如果,你的,项目,是,前端,项目,用,Webpack,等,打包工具,一般,用,es6,或者,es2015,这样,打包工具,能,做,Tree Shaking,优化,打包,体积。

如果,你的,项目,需要,同时,支持,浏览器,和,Node.js,可以,用,umd

3. strict

strict,是,严格模式,的,总开关。开启,之后,会,启用,所有,严格的,类型检查,选项。

包括:

  • noImplicitAny:不允许,隐式的,any类型。
  • strictNullChecks:严格的,null检查。
  • strictFunctionTypes:严格的,函数类型,检查。
  • strictBindCallApply:严格的,bind/call/apply,检查。
  • strictPropertyInitialization:严格的,属性初始化,检查。
  • noImplicitThis:不允许,隐式的,this类型。
  • alwaysStrict:总是,在,严格模式,下,编译。

开启,strict,能,让,类型检查,更,严格,发现,更多的,潜在,bug。但是,也,会,增加,开发的,难度,因为,你,需要,更,精确地,定义,类型。

对于,新项目,推荐,开启,strict。虽然,一开始,可能,觉得,麻烦,但是,长期来看,能,提高,代码,质量,减少,bug。

对于,老项目,从,JavaScript,迁移,到,TypeScript,可以,先,不开启,strict,等,类型,定义,完善了,再,慢慢,开启。

4. include

include,指定,需要,编译的,文件,或,目录。

比如,"include": ["src/*/"],表示,编译,src,目录,下的,所有,文件。

*,表示,任意,层级的,子目录。,表示,任意,文件名。

除了,include,还有,exclude,指定,不需要,编译的,文件,或,目录。比如,"exclude": ["node_modules", "dist"]

一般,include,和,exclude,配合,使用,来,确定,编译,范围。

三、常用的编译选项

说完了,最基础的,配置,再,说说,常用的,编译选项。

1. outDir

outDir,指定,编译,后的,文件,输出,目录。

比如,"outDir": "./dist",表示,编译,后的,文件,都,输出到,dist,目录。

如果,不设置,outDir,编译,后的,文件,会,和,源文件,放在,同一个,目录,会,比较,乱。所以,一般,都,建议,设置,outDir

2. rootDir

rootDir,指定,源文件,的,根目录。

它,会,影响,编译,后的,目录结构。比如,如果,rootDir,是,src,那么,src,目录,下的,文件,编译后,会,保持,相对,src,的,目录结构,输出到,outDir

如果,不设置,rootDir,TypeScript,会,自动,计算,一个,根目录,基于,include,的,文件。

一般,建议,显式,设置,rootDir,避免,目录结构,混乱。

3. sourceMap

sourceMap,指定,是否,生成,source map,文件。

source map,是,用来,调试的。它,能,把,编译,后的,JavaScript,代码,映射回,原始的,TypeScript,代码。这样,调试的时候,你,看到的,是,TypeScript,代码,而不是,编译,后的,JavaScript,代码。

对于,开发环境,推荐,开启,sourceMap,方便,调试。

对于,生产环境,可以,不开启,或者,生成,单独的,source map,文件,不,发布到,线上。

4. declaration

declaration,指定,是否,生成,类型声明,文件(.d.ts)。

如果,你的,项目,是,一个,库,需要,给,别人,用,那么,推荐,开启,declaration,生成,类型声明,文件。这样,别人,用,你的,库,的时候,就能,有,类型提示,和,类型检查。

如果,你的,项目,是,一个,应用,不是,库,那么,一般,不需要,开启,declaration

5. declarationDir

declarationDir,指定,类型声明,文件,的,输出,目录。

如果,开启了,declaration,可以,用,declarationDir,指定,类型声明,文件,输出到,哪里。

如果,不设置,类型声明,文件,会,和,编译,后的,JavaScript,文件,放在,一起。

6. esModuleInterop

esModuleInterop,指定,是否,启用,ES模块,互操作性。

开启,之后,能,让,TypeScript,更好地,处理,CommonJS,模块,和,ES模块,之间的,互操作。

比如,你,用,import React from 'react',导入,一个,CommonJS,模块。如果,不开启,esModuleInterop,可能,会,报错,或者,需要,用,import * as React from 'react'

开启,esModuleInterop,之后,就能,用,import React from 'react',这种,更,自然的,方式,导入。

一般,推荐,开启,esModuleInterop

7. allowSyntheticDefaultImports

allowSyntheticDefaultImports,指定,是否,允许,从,没有,默认导出的,模块,默认导入。

它,和,esModuleInterop,类似,但是,只,影响,类型检查,不,影响,编译,输出。

一般,开启了,esModuleInterop,就,自动,开启了,allowSyntheticDefaultImports

四、类型检查选项

再,说说,类型检查,相关的,选项。

前面,说过,strict,是,严格模式,的,总开关,开启,之后,会,启用,所有,严格的,类型检查,选项。

但是,有时候,你,可能,不想,开启,所有的,严格选项,只想,开启,其中的,几个。这时候,就,可以,单独,设置,每个,选项。

1. noImplicitAny

noImplicitAny,不允许,隐式的,any,类型。

如果,一个,变量,或者,函数参数,没有,明确,指定,类型,TypeScript,会,推断,它,的,类型。如果,推断不出来,就,会,认为,是,any,类型。

开启,noImplicitAny,之后,如果,有,隐式的,any,类型,就,会,报错。

这,能,强制,你,给,所有的,变量,和,函数参数,定义,类型,减少,因为,类型,不明确,导致的,bug。

2. strictNullChecks

strictNullChecks,严格的,null,和,undefined,检查。

如果,不开启,strictNullChecksnull,和,undefined,可以,赋值给,任何,类型的,变量。比如,let name: string = null,是,合法的。

但是,这,很,容易,导致,运行时,错误。比如,你,以为,name,是,字符串,调用,name.length,但是,实际上,name,是,null,就,会,报错。

开启,strictNullChecks,之后,null,和,undefined,不能,随便,赋值给,其他,类型。你,需要,明确,处理,null,和,undefined,的,情况。

这,能,大大,减少,因为,null,和,undefined,导致的,运行时,错误。

3. noUnusedLocals

noUnusedLocals,不允许,有,未使用的,局部变量。

如果,你,声明了,一个,局部变量,但是,没有,使用,就,会,报错。

这,能,帮,你,发现,无用的,代码,保持,代码,整洁。

4. noUnusedParameters

noUnusedParameters,不允许,有,未使用的,函数参数。

如果,一个,函数,有,参数,但是,没有,使用,就,会,报错。

这,也,能,帮,你,发现,无用的,代码。

但是,有时候,函数参数,是,必须的,比如,接口,实现,回调函数,等等,虽然,没用到,但是,不能,删。这时候,可以,在,参数名,前面,加,下划线,比如,_unused,来,跳过,检查。

5. noImplicitReturns

noImplicitReturns,不允许,函数,有,隐式的,返回。

如果,一个,函数,在,某些,分支,返回了,值,在,另一些,分支,没有,返回,就,会,报错。

这,能,确保,函数,总是,返回,一致的,类型,减少,因为,返回值,不一致,导致的,bug。

6. noFallthroughCasesInSwitch

noFallthroughCasesInSwitch,不允许,switch,语句,中,有,case,穿透。

如果,一个,case,没有,break,或者,return,就,会,继续,执行,下一个,case,这,叫,case穿透。

开启,这个,选项,之后,如果,有,case穿透,就,会,报错。

这,能,减少,因为,忘记,写,break,导致的,bug。

五、模块和路径选项

再,说说,模块,和,路径,相关的,选项。

1. baseUrl

baseUrl,指定,模块,解析,的,基础,路径。

开启,之后,你,可以,用,非相对路径,来,导入,模块。

比如,baseUrl,是,src,那么,你,可以,用,import utils from 'utils',来,导入,src/utils,模块,而不用,写,import utils from '../../utils',这种,很长的,相对路径。

这,能,让,导入,语句,更,简洁,也,更,容易,重构。

2. paths

paths,指定,模块,路径,的,映射。

它,需要,和,baseUrl,一起,使用。可以,把,一些,常用的,路径,映射成,简短的,别名。

比如:

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"],
      "@components/*": ["components/*"],
      "@utils/*": ["utils/*"]
    }
  }
}

这样,你,就,可以,用,import Button from '@components/Button',来,导入,src/components/Button,模块。

这,能,让,导入,语句,更,简洁,也,更,有,语义。

但是,注意,paths,只是,TypeScript,的,类型检查,层面的,路径映射。编译,后的,JavaScript,代码,里,还是,原来的,路径。如果,你,用,Webpack,等,打包工具,需要,在,打包工具,里,也,配置,对应的,路径别名,才能,正常,工作。

3. moduleResolution

moduleResolution,指定,模块,解析,策略。

可选值,有:node,和,classic

一般,推荐,用,node,因为,它,和,Node.js,的,模块,解析,策略,一致,更,常用。

classic,是,比较,老的,解析,策略,现在,一般,不用了。

4. resolveJsonModule

resolveJsonModule,指定,是否,允许,导入,JSON,文件。

开启,之后,你,可以,用,import data from './data.json',来,导入,JSON,文件,而且,会,有,类型提示。

这,很,方便,比如,导入,配置文件,或者,静态数据。

六、高级配置

最后,说说,一些,高级的,配置。

1. lib

lib,指定,编译,时,需要,包含的,库,文件。

库,文件,定义了,JavaScript,运行时,的,类型,比如,ArrayObjectPromiseMapSet,等等,还有,DOM,的,类型,比如,documentwindow,等等。

lib,的,可选值,有,很多,比如:

  • es5es6es2015es2016es2017es2018,等等:对应,不同,ES版本,的,类型。
  • dom:DOM,的,类型。
  • dom.iterable:DOM,可迭代,的,类型。
  • webworker:Web Worker,的,类型。
  • scripthost:脚本宿主,的,类型。

如果,不设置,lib,TypeScript,会,根据,target,自动,选择,默认的,库。

但是,有时候,你,可能,需要,自定义。比如,你的,target,是,es5,但是,你,想用,Promise,这,是,es6,的,特性。这时候,你,就,可以,在,lib,里,加上,es2015.promise

再比如,你的,项目,是,Node.js,项目,不需要,DOM,类型。这时候,你,可以,在,lib,里,去掉,dom,避免,全局,有,document,等,DOM,变量,导致,类型,污染。

2. typeRoots和types

typeRoots,指定,类型声明,文件,的,搜索,目录。

默认,TypeScript,会,在,node_modules/@types,目录,下,搜索,类型声明,文件。

如果,你,有,一些,自定义的,类型声明,文件,可以,用,typeRoots,指定,搜索,目录。

types,指定,需要,包含的,类型声明,包。

默认,TypeScript,会,包含,typeRoots,下的,所有,类型声明,包。

如果,你,只想,包含,部分,类型声明,包,可以,用,types,指定。比如,"types": ["node", "jest"],表示,只,包含,node,和,jest,的,类型声明。

3. skipLibCheck

skipLibCheck,指定,是否,跳过,对,.d.ts,文件,的,类型检查。

开启,之后,TypeScript,不会,检查,类型声明,文件,的,类型,错误。

这,能,加快,编译,速度,也,能,避免,因为,第三方,库,的,类型声明,有,问题,导致,编译,失败。

一般,推荐,开启,skipLibCheck

4. forceConsistentCasingInFileNames

forceConsistentCasingInFileNames,指定,是否,强制,文件名,大小写,一致。

开启,之后,如果你,导入,文件,的时候,大小写,和,实际,文件名,不一致,就,会,报错。

这,很,重要,因为,在,Windows,和,macOS,上,文件名,大小写,不敏感,import './Utils',和,import './utils',都,能,找到,文件。但是,在,Linux,上,文件名,大小写,敏感,就,会,找不到。

开启,这个,选项,能,确保,代码,在,不同的,操作系统,上,都,能,正常,工作。

一般,推荐,开启,forceConsistentCasingInFileNames

5. incremental

incremental,指定,是否,启用,增量编译。

开启,之后,TypeScript,会,保存,编译,的,状态,到,一个,.tsbuildinfo,文件。下次,编译,的时候,只,编译,变化的,文件,能,大大,加快,编译,速度。

对于,大项目,推荐,开启,incremental,能,显著,提高,开发,效率。

七、一个完整的配置示例

最后,给,大家,一个,比较,完整的,配置示例,供,参考。

这是,一个,前端,项目,的,tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "es2015",
    "moduleResolution": "node",
    "lib": ["es2017", "dom", "dom.iterable"],
    "strict": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "resolveJsonModule": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"],
      "@components/*": ["components/*"],
      "@utils/*": ["utils/*"]
    },
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "incremental": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

这个,配置,开启了,严格模式,和,很多,有用的,检查,选项,能,保证,代码,质量。同时,也,配置了,路径别名,增量编译,等,提高,开发,效率的,选项。

大家,可以,根据,自己的,项目,情况,调整,这个,配置。

八、写在最后

以上,就是,TypeScript,入门配置,的,详细,讲解,从基础,到高级。

TypeScript,的,配置,选项,确实,很多,刚开始,接触,的时候,容易,晕。但是,只要,理解了,每个,选项,的,意思,和,作用,就,不难了。

而且,大部分,项目,常用的,选项,也就,那么,十几个。其他的,选项,用得,不多,需要的时候,再,查,文档,就,可以了。

希望,这篇,文章,能,帮,大家,搞懂,TypeScript,的,配置。如果,有,什么,问题,或者,不同的,看法,欢迎,在,评论区,留言,我们,一起,交流。

最后,祝,大家,都,能,用好,TypeScript,写出,更高质量的,代码。