🙋 Why CheckJS?
-
让 JavaScript 项目也能享受到 TS 的类型推导等诸多好处。
-
和直迁 TypeScript 相比,大大降低成本和风险,例如:
🚥 使用方法
安装依赖、追加配置
# 为你的项目安装 TypeScript
npm install typescript
# 可选的:类型定义文件,按照自己的项目需要酌情添加
npm install @types/react -D
根目录新建 tsconfig.json,复制以下内容,特别注意黄色加粗的内容:
{
"compilerOptions": {
"outDir": "./FAKE_DIR",
"target": "ESNext",
"module": "CommonJS",
"esModuleInterop": true,
"allowJs": true,
"checkJs": true,
"strictNullChecks": true,
"jsx": "react"
},
"include": [
// 根据项目目录结构自行配置
"./src/**/*"
],
"exclude": [
// 根据项目特性按需添加
]
}
编写类型定义
编写类型定义的时候最好有一定的约束,可以防止类型定义冲突、提高代码结构可读性,我个人推荐下面这些方案。
目录结构
一般来说,类型定义都是针对一个页面或者组件的(我们可以拿 CSS 样式文件的地位来做类比),即和组件(页面)所在文件在同一目录下,命名为 typings.d.ts:
└── src
└── pages
├── detail
│ ├── components
│ │ └── Alert
│ │ ├── index.jsx
│ │ ├── style.scss
│ │ └── typings.d.ts
│ ├── index.jsx
│ ├── style.scss
│ └── typings.d.ts
├── home
└── profile
书写细节
d.ts 文件和 ts 文件一个最大的不同就是前者无需导入,可以理解为全局变量,这就很容易导致同名类型定义的冲突干扰,为了尽可能地解决该问题,推荐使用 namespace (命名空间)语法来分组局部类型定义,还是以上面一小节的目录结构为例,Alert 组件的 typings.d.ts 可以这样写:
declare namespace Page.Detail.Components.Alert {
interface AlertProps {
/**
* 警告内容
*/
message: string;
/**
* 当关闭时做些什么
*/
onClose: () => void;
}
}
原则是根据其所属的页面、组件等层级关系划分,层级之间可以用点号隔开,以防止过多的嵌套影响美观。
其他的书写细节和 TypeScript 的写法别无二致,平时 ts 怎么写的,这里如法炮制即可。
消费方式
核心思路是通过编写 jsdoc 进行消费,下面提供几个不错的实践:
案例 1:React 函数式组件
下面的案例中使用了 React.FC 工具函数来实现。(提示:TypeScript 的各种内置 Utility Types 也是可以利用起来的)
/**
* Alert 组件,用来展示警告信息,如:网络错误、服务器错误等
*
* @type {React.FC<Page.Detail.Components.Alert.AlertProps>}
*/
export const Alert = (props) => {
const { message, onClose } = props;
return <div>hello world!</div>;
};
案例 2:普通函数
对于一般函数,使用 @param 或者 @returns 注释描述该函数的入参和返回值。
/**
* 获取用户信息描述
*
* @param {Page.Detail.Components.Alert.UserInfo} userInfo 用户信息
* @return {string} 用户信息描述
*/
const getUserDescription = (userInfo) => {
const {name, age} = userInfo;
return `我是 ${name}, 今年 ${age} 岁`;
}
案例 3:一般变量
对于一般变量,可以使用 @types 注释在该变量顶部描述:
/** @type {Page.Detail.Components.Alert.UserInfo} */
const userInfo = {
age: 18,
name: '张三',
}
// 或者也可以这样写
const user = /** @type {Page.Detail.Components.Alert.UserInfo} */ {
age: 18,
name: '张三',
};
// 在 return 语句中
return /** @type {Page.Detail.Components.Alert.UserInfo} */ {
age: 18,
name: '张三',
};
最后,配合 VSCode、WebStorm 等现代 IDE,你就可以享受到类型提示了!
🚨 使用时的陷阱
无法识别 d.ts 中定义的 namespace
先引入一个问题:
已知某项目是一个巨大的 monorepo,请看它的 tsconfig.json,它有什么潜在的问题?
{
"compilerOptions": {
"outDir": "./FAKE_DIR",
"target": "ESNext",
"module": "CommonJS",
"esModuleInterop": true,
"allowJs": true,
"checkJs": true,
"jsx": "react"
},
"include": [
"./packages/**/*",
"./typings"
]
}
-
大部分情况下,monorepo 仓库下的构建产物(大部分是特别长的 JavaScript 文件)都会被打包到各自 package 下面,很容易被 include 字段匹配。
-
一旦开启 allowJs = true,TypeScript Server 就会去检查这些不必要的 JavaScript 文件,从而触发性能阈值,失去某些 feature,例如我们上文的 JavaScript Check 能力。
那么如何去看是什么文件造成了此问题?这里提供一个非常优雅的解法:
- 首先打开 TS Sever Log:
- 在大概第三十行左右的位置就可以看到 TypeScript Checker log 了所有被解析的 build 产物,这些都应该被排除在外:
在上面这个 Case 中,就可以通过 exclude 字段排除掉所有的 build 目录来提高性能:
{
"compilerOptions": {
// 略去
},
"include": [
"./packages/**/*",
"./typings"
],
"exclude": [
"**/build/**"
]
}