本文首发微信公众号「码上花甲」。
近期社区中不少工具放弃了 TypeScript 转而用上了 JsDoc。
咱不评价这种做法是否值得推荐,但是,有一些项目从一开始就没有使用 TypeScript。这种情况下直接迁移到 TypeScript 成本会很高,那能否用 JsDoc 来"代替" TypeScript 呢?
答案是可行的。
我们不能因为 TS 而一定要用 TS,必须要结合项目实际情况来确定。
不过 JsDoc 也有很多细节,是你可能没注意到的。除了静态类型检查之外,JsDoc 的能力还是蛮强大的。
今天,我们就结合 TS 来看下 JsDoc 都能干哪些事情。
1. 变量的类型
在 TS 中,要给一个变量定义类型很简单:
js
let name: string = 'Randal';
使用 TS,直接在变量 name
后面定义为 string
类型即可。
JsDoc 也能做到:
js
/** @type {string} */
let name = 'Randal';
编辑器同样可以识别出 JsDoc 注释的变量类型:
像其他基本类型,JsDoc 也都支持,比如 number
类型。
js
/** @type {number} */
let age = 30;
除了上面这种简单的基本类型之外,JsDoc 还支持联合类型。在 TS 中的写法是这样的:
js
const personName: 'Randal' | 'Olive' | 'Jack' = 'Randal';
JsDoc 的写法稍微麻烦一点,但也能很好的支持:
js
/**
* @typedef {'Randal' | 'Olive' | 'Jack'} PersonName
*/
/**
* @type {PersonName}
*/
const personName = 'Randal';
同样的,编辑器也能正确识别出对应的类型:
对于普通的变量,在 JsDoc 中可以用 @type
标签描述变量的类型;对于联合类型可借助 @typeof
标签辅助完成。其他的基本类型写法都差不多,就不一一展示了。
2. 函数参数和返回值
函数是 JS 中的一等公民,其重要性不言而喻。我们常见的类型定义一般都见于函数声明。
要使用 JsDoc 给函数参数定义类型,需要用到 param
标签。
比如下面的函数,用 TS 表示是这样的:
js
// Say hi to someone.
function sayHiSomeone(name: string) {
console.log('Hi ' + name);
}
很简单,写法类似于给变量定义类型,直接在函数参数后面给定相应的类型即可,上面的是 string
类型。
JsDoc 的话不难,只不过需要稍微多写一点注释:
js
/**
* Say hi to someone.
* @param {string} name - the name of somebody(with type and description)
*/
function sayHiSomeone(name) {
console.log('Hi ' + name);
}
@param
标签就表示给函数的参数定义类型和描述,{}
中的就是参数 name
的类型,后面的字符串是该参数的描述信息。
鼠标悬停在函数上,会显示函数的类型定义:
而函数的返回值被隐式地定义为了 void
类型。
也对,函数体内确实没有 return
数据出去,说明推断的是正确的。
如果要显式地定义函数返回值该怎么做呢?
js
/**
* Say hi to someone.
* @param {string} name - the name of somebody(with type and description)
* @return {undefined}
*/
function sayHiSomeone(name) {
console.log('Hi ' + name);
return undefined;
}
可以用 @return
标签表示函数的返回值,{}
中定义函数返回值类型:
上面演示的都是简单类型的定义,对于稍微复杂一点的------比如对象类型或数组类型该怎么办呢?
下面是一个 TS 示例:
ts
interface EmployeeType {
name: string;
department: string;
}
function Project() {}
Project.prototype.assignment = function(employee: EmployeeType) {
console.log(employee.name + ' works in ' + employee.department);
};
借助 TS 的 interface
很容易就能定义参数 employee
的类型,在 JsDoc 中的写法却有点不一样:
js
function Project() {}
/**
* 记录参数属性
* @param {Object} employee - the employee for assignment
* @param {string} employee.name - the name of employee
* @param {string} employee.department - the department of employee
*/
Project.prototype.assignment = function(employee) {
console.log(employee.name + ' works in ' + employee.department);
};
可以看到第一个 @param
标签的类型是 Object
,表示 employee
参数是一个对象类型。
然后,再以此定义这个对象类型中成员的类型,第二个和第三个 @param
标签定义的都是 string
类型。
不过,这里要注意的一点是,@param
标签类型后的 employee
必须要保持和函数参数名一致,编辑器才能识别出来。
数组参数类型的写法是差不多的,比如:
js
/**
* 记录数组中值的属性
* @param {Object[]} employees - the employees params
* @param {string} employees[].name - the name of employee
* @param {string} employees[].department - the department of employee
*/
Project.prototype.employees = function(employees) {
console.log(employees[0].name + ' works in ' + employees[0].department);
};
还有一种常见的场景,JsDoc 如何表示函数的参数是可选的呢?JsDoc 也考虑到这种情况了,用 []
将参数名包裹起来就表示该参数是可选的:
js
/**
* 可选参数
* @param {string} [somebody] - somebody's name
*/
function sayHello(somebody) {
if (!somebody) {
somebody = 'Randal Wang';
}
console.log('Hello ' + (somebody || 'me'));
}
如果参数 somebody
没有定义,那么其类型应该是 undefined
:
最后,一种常见的场景式函数参数是一个回调函数。回调函数在函数体内被调用:
js
/**
* 回调函数
* @param {requestCallback} cb - the callback that handles the response
*/
function callback(cb) {
if (typeof cb === 'function') {
cb();
}
}
鼠标悬浮在函数上的效果如下:
3. async
async
函数表示该函数是异步的,比如在一个函数体内返回 Promise
。用 JsDoc 来描述也不难:
js
/**
* Download data from specitic url
* @async
* @param {string} url - the url to download data
* @return {Promise<string>} the data from url
*/
async function downloadData(url) {
return new Promise((resolve, _reject) => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
效果如下:
4. JsDoc 其他标签
除了上面提到的这几个标签较为常用之外,JsDoc 还提供了很多丰富、使用的标签,比如 @see
、@link
、@abstract
、@description
、@todo
等等。
虽然较之 TS 的能力还有不少差距,但是也已经能覆盖大部分场景了。
JsDoc 最大的特点是不会侵入到业务代码中,它是以正常的注释的形式存在的。要知道,我们即便用 TS 也是要写注释的。
所以,如果你的项目本来就是用 JS 写的,不妨先把 JsDoc 用起来。将来有机会的时候,再将其迁移到 TS 不失为一个好的选择。
上面仅仅提供几个代码片段,作为抛砖引玉吧算是。如果你对 JsDoc 其他标签还不熟悉,可以去看看官网的示例,很全面。
也可以参考和今天内容配套的源码,有一些常用标签的代码示例供你参考。
好了,今天的内容就先到这里吧,感谢阅读,下期再见 ;-)
为便于理解,给大家准备了源码参考:
bash
$ npm install -g agelesscoding/cli # 安装公众号配套的 agc 脚手架
$ agc init # 在列表中,选择对应的源码示例,比如 20231001 表示 2023 年 10 月 01 号的文章源码