很多时候,我们不得不使用 JavaScript 而非 TypeScript 开发项目,作为一个忠实的 TypeScript 用户,我根本无法接受 JS 代码的开发体验,于是开始研究如何在不影响构建流程(已有项目太大,增加 TS 的修改和测试成本太高)的前提下,以影响尽量小的方式为代码增加静态类型检查。
TOC
使用 JSDoc 添加类型信息
比较常见的做法是使用 JSDoc 为 JS 代码添加一定的类型提示,例如:
/**
* @param {Object} params 函数的参数
* @param {string} params.requiredProp 必传
* @param {boolean} [params.optionalProp] 可选
*
* @returns {number} 返回值
*/
export function func(params) {
return 'no error';
}
这样在 hover 查看和使用函数时可以有相应的函数签名提示:
然而,单纯的使用 JSDoc 并不会对代码有任何实际的约束,即使不按签名来传参也不会有问题。参数类型、返回值类型都不一致,也不会有任何的提示:
对 JS 代码开启静态类型检查
只是简单提示显然不能满足我们的需求,理想情况下是能在编码过程中对所有类型不兼容的地方进行提示(就像 Typescript 代码一样)。
实际上在 Typescript 2.3 以后已经增加了对 js 代码的类型检查和错误提示:
- 如果要针对所有的 js 代码开启,则可以在 tsconfig.json 中将 allowJS 和 checkJs 设置为 true
- 如果只针对部分文件开启,可以在想要进行类型检查的 js 文件头部添加注释
// @ts-check
对于已有项目,一般选择第二种方式,效果如下:
这个提示只是 VSCode 编辑器的一个检查,不影响构建过程(除非项目本身的构建流程就包含了 Typescript 编译且 allowJs 和 checkJs 为 true),因此可以放心的在代码库的任意地方进行使用。
与 Typescript 结合
通过上两节的设置以及可以在 VSCode 中对 JavaScript 代码进行静态检查了,但是还有些问题:
- JSDoc 书写过于繁琐,而且和 Typescript 的类型写法差异比较大,有一定的理解成本
- JSDoc 的类型不方便共享,例如很难将一个类型定义一次书写多次使用
- 。。。
实际上相比单纯的 JSDoc 写法,还有一种更简洁的结合 Typescript 的写法。主要是用到了 JSDoc 的 import。
首先需要单独写一个 ts 文件,例如我一般取名叫 interface.ts
或者 xxx.interface.ts
然后代码中通过 import 的方式进行引用:
返回值等也可以用类似的方式引用 ts 文件中导出的类型,同时这个类型文件也可以在多处任意引用。
具体应用举例
React 组件
/**
* @typedef {import('./interface').Props} Props
* @typedef {import('./interface').State} State
* @extends {React.Component<Props, State>}
*/
export default class Component extends React.Component {
/** @type {State} */
state = {
someState: 1,
};
}
/**
* @typedef {import('./interface').Props} Props
* @extends {React.FC<Props>}
*/
const FunctionComponent = props => <div>{props}</div>;
配置文件
可以为各种配置文件提供类型检查和输入的提示
babel.config.js
/**
* @type {import('@babel/core').ConfigFunction}
*/
module.exports = api => {
return {
presets: [],
};
};
如果是直接导出 object 的形式可以用:
/**
* @type {import('@babel/core').TransformOptions}
*/