大家好,我是刘布斯。
前几天帮团队里新来的小伙 review 代码,发现他提交的 PR 里有个 TypeScript 类型问题折腾了半天。
我随口问了句:"你 tsconfig 里配了 strict 模式没?" 结果这哥们一脸茫然地看着我——又是一个被 create-react-app 这类脚手架惯坏的孩子。
说起来,我刚接触 TypeScript 那会儿(那时候还是 1.6 版本),配置 tsconfig.json 简直就是玄学。现在十年过去了,这玩意儿虽然文档越来越完善,但选项也越来越多,今天就跟大家聊聊那些真正影响日常开发的配置项。
早在 2017 年,我们团队决定全面转向 TypeScript 时,第一个争议就是要不要开 strict 模式。当时团队里有个 Angular 老司机坚持要开,而其他从 JavaScript 转过来的同事(包括我)都觉得这玩意儿太烦人——一个简单的对象字面量都要写类型断言,这不是自虐吗?
但是三个月后我们项目遇到个生产环境 bug,就是因为一个可能是 undefined 的值没做检查。那天晚上十点,我们一群人围着电脑查问题的时候,那位 Angular 老司机就站在后面幽幽地说:"要是开了 strictNullChecks..."
现在我的原则很简单:新项目无脑开 strict,老项目逐步开。这个复合选项包含的几个子项都特别实用:
{
"compilerOptions": {
"strict": true,
// 相当于同时开启:
// "noImplicitAny": true,
// "strictNullChecks": true,
// "strictFunctionTypes": true,
// "strictBindCallApply": true,
// "strictPropertyInitialization": true,
// "noImplicitThis": true,
// "alwaysStrict": true
}
}
特别是 strictNullChecks,它能帮你避免"undefined is not a function"这种经典错误。虽然刚开始写代码会多花 10% 的时间,但调试省下的时间绝对不止这个数。
去年我们有个项目要把部分代码共享给后端团队用,结果他们那边死活编译不过。折腾了一下午才发现,我们用的是 "node" 解析策略,他们那用的是 "classic"。这个配置项决定了 TypeScript 怎么查找模块:
{
"compilerOptions": {
"moduleResolution": "node" // 或者是 "classic"
}
}
简单点说,就是告诉TypeScript:"当你看到import 'lodash'
这种语句时,该去哪儿找这个模块"。就像快递员送包裹,得知道是按门牌号逐户找(classic)还是直接查物业登记表(node)。
它有两个主要候选值:
"node"
(现代项目首选)
node_modules/lodash
,再找package.json
里指定的main
或module
字段/index.ts
这类默认文件import axios from 'axios'
时,它会沿着目录向上递归查找node_modules
,就像Node.js真正运行时那样"classic"
(上古遗产)
node_modules
import axios from '../node_modules/axios'
这种反人类的路径还有两个算比较常用的值:
"node16"
/"nodenext"
:Node.js的ESM模块解析规则,处理.mjs
/.cjs
扩展名区分(TypeScript 4.7+)"bundler"
:专为现代打包工具设计的模式,要求配合"module": "esnext"
使用(TypeScript 5.0+)现在的项目基本上都用 "node",除非你在搞什么上古时代的遗产代码。但有意思的是,create-react-app 生成的 tsconfig 里从来不显式写这个配置,因为他们的 webpack 配置里已经处理好了——这也就是为什么很多前端同学不知道这回事。
我见过最夸张的相对路径是这样的:
import { Button } from '../../../../../components/ui/Button'
这种写法的可读性太差了,其实可以用 baseUrl + paths 解决:
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}
这样导入就清爽多了:
import { Button } from '@components/ui/Button'
不过要注意,这只是一个 TypeScript 的编译时特性。如果你用的打包工具(比如 webpack)不认识这个配置,还得在对应的配置里再加一遍。比如我们的 Vue 项目需要在 vite.config.ts
里加了 alias 配置才行。
TypeScript 3.4 引入了 incremental 编译,理论上能大幅提升编译速度。但实际用起来...emmm,看人品。
{
"compilerOptions": {
"incremental": true
}
}
这个选项会让 TypeScript 生成 .tsbuildinfo 文件来存储编译信息。理论上第二次编译会快很多,但我们那个 monorepo 项目里,有时候这个缓存文件会出问题,反而导致编译失败。我的经验是:中小型项目大胆开,大型 monorepo 项目...做好随时删 .tsbuildinfo 文件的准备。
esModuleInterop:如果你曾经被 import * as React from 'react'
这种写法恶心到过,这个配置能让你回归正常的 import React from 'react'
skipLibCheck:跳过声明文件的类型检查,能显著提升编译速度,特别是当你用了很多第三方库时。虽然理论上可能漏掉一些类型错误,但五年了我还没遇到过因此产生的问题
forceConsistentCasingInFileNames:这个配置特别适合我们团队那个总在 Mac 和 Windows 之间切换的倒霉同事。开启后,文件名大小写不一致直接报错,避免你在 Mac 上开发好好的,部署到 Linux 服务器上就挂掉
配置这东西没有标准答案,我们团队现在的策略是:
TypeScript 的配置就像装修房子,刚开始总觉得越多越好,后来才发现简洁实用最重要。毕竟,我们的目标是写业务代码,不是玩配置杂技,对吧?
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打无广告和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。