关于JSDoc和它的基本语法支持,请参考官方文档:http://usejsdoc.org/。虽说官方文档还是比较全的,实际使用中还是需要摸索一下适合自己项目的写法,并考虑最终文档生成工具的支持。
假设我们有个需要Object参数的方法。可以这样写:
/**
* 把两个数字a和b加起来
* @param {Object} args
* @prop {number} [args.a] 我是a
* @prop {number} [args.b=0] 我是b,默认值为0
* @return {number} 返回a+b
*/
function func({ a, b = 0 }) {
return a + b;
}
这种写法不允许其他部分重复利用args的结构。
也可以将args独立声明成一个类型:
/**
* @typedef {Object} FuncArgs
* @prop {number} [a] 我是a
* @prop {number} [b=0] 我是b,默认值为0
*/
/**
* 把两个数字a和b加起来
* @param {FuncArgs} args
* @return {number} 返回a+b
*/
function func({ a, b = 0 }) {
return a + b;
}
这种写法通常用于声明全局或模块通用的类型。
也可以将FuncArgs声明为func的内部类型:
/**
* @typedef {Object} func~FuncArgs
* @prop {number} [a] 我是a
* @prop {number} [b=0] 我是b,默认值为0
*/
/**
* 把两个数字a和b加起来
* @param {func~FuncArgs} args
* @return {number} 返回a+b
*/
function func({ a, b = 0 }) {
return a + b;
}
这种写法与同样是为func内部类型的第一种写法比起来,更具可读性。但有个个人不太喜欢的地方就是最终生成的文档会将function和参数的类型分成两个部分,对阅读这种文档结构不熟悉的人来说不是很友好。
首先,官方设定了一个@callback
关键字:
/**
* @param {func~myCallback} cb
* @param {number} num
* @return {number}
*/
function func(cb, num) {
return cb(num);
}
/**
* @callback {Function} func~myCallback
* @param {number} a
* @return {number}
*/
不过同例1一样,也可以利用@typedef
关键字:
/**
* @param {func~myCallback} cb
* @param {number} num
* @return {number}
*/
function func(cb, num) {
return cb(num);
}
/**
* @typedef {Function} func~myCallback
* @param {number} a
* @return {number}
*/
这两种基本上是一样的,使用@callback
更符合语义。另外一点是,这种回调方法一般很少通用,所以建议声明为调用方法的内部类型。
/** @module testmodule */
/**
* @param {number} a I am argument a.
* @return {string} Return description.
**/
export default function(a, b) {
return '' + a + b;
}
jsdoc-to-markdown生成结果:
使用@alias
:
/** @module testmodule */
/**
* @alias module:testmodule
* @param {number} a
* @return {string} b
**/
function test(a, b) {
return '' + a + b;
}
export default test;
jsdoc-to-markdown生成结果跟上面一样。
有趣的是,如果直接export的不是匿名方法,结果会不一样。
/** @module testmodule */
/**
* @param {number} a I am argument a.
* @return {string} Return description.
**/
export default function test(a, b) {
return '' + a + b;
}
jsdoc-to-markdown生成结果:
jsoc-to-markdown的wiki给了一个非常简单的例子,但只处理了整个文件没有default export的情况: https://github.com/jsdoc2md/jsdoc-to-markdown/wiki/How-to-document-an-ES2015-module-(multiple-named-exports)。
假设有一个ES6文件:
/** @module testmodule */
/**
* @const
*/
export const testConst = 1;
/**
* @enum
*/
export const testEnum = {
'JS': 1,
'PYTHON': 2,
'C++': 3
};
/**
* Function export
*/
export function func() {}
/**
* @param {number} a I am argument a.
* @return {string} Return description.
**/
export default function test(a, b) {
return '' + a + b;
}
生成结果如下:
这就让人很不解了,为啥要把那些跟default无关的东西放在default下面,混淆视听。
但如果把default export去掉就好了:
但现实是这两种有可能同时出现,我没有找到很好的解决办法,目前只能通过避免出现这种写法或者手动加@memberof module:testmodule
,或者更改文档生成模版来修正。
## 例5: 使用Babel
JSDoc还不能聪明到解析ES6代码,为了能让它解析ES6,需要借助于Babel,和插件jsdoc-babel,当然还有相应的Babel preset和插件。
首先安装这些依赖:
npm i --save-dev babel-core babel-preset-env jsdoc jsdoc-babel
然后需要一个JSDoc配置文件, .jsdoc.json(随便什么名字):
{
"plugins": ["node_modules/jsdoc-babel"]
}
还要一个.babelrc,根据自己的代码来选择preset和插件:
{
"presets": [
[
"env",
{
"targets": {
"browsers": ["last 2 versions", "safari >= 7"]
},
"modules": false
}
]
]
}
然后运行:
npm run jsdoc -c .jsdoc.json
并不是很清楚具体的触发时机,可能是个bug?碰到这种我只能认命加/** @static */
来修正结果。
用ES6写JS最大的好处恐怕就是从此多了无数个文件,每个文件里都只是很小一块代码,从此找代码要靠IDE跳转或全文搜索了。So我会希望将一个文件夹作为一个模块,所有此文件夹下的文件通过index.js来选出该模块开放给别人用的东西。
然后我就发现用JSDoc生成文档的时候,要做到这点比较难。
第一是JSDoc似乎并不能识别文件路径,如果要将多个文件声明属于同一个模块,只有在每个文件头部手动加上/** @module ModuleName */
。
但这一切,还需要模版的帮忙。我试用了nijikokun/minami和tui.jsdoc-template。tui.jsdoc-template很好的实现了我想要的这种树状的结构效果:
还有要考虑的一点是/** @enum */
这个关键字,个人认为是应该要在生成文档时列举出声明的键和值的。但同样的,要看模版支不支持。
flow的支持也是简单地靠Babel插件transform-flow-strip-types来完成。
特地拿出来讲是因为我曾很美好地设想可以直接在flow type的声明下面,加上结构特别类似的JSDoc和必要的描述说明文字。把flow type声明转成JSDoc这一步甚至可以完全通过脚本自动完成,会省不少力气。
结果发现如果一个文件纯粹只有flow声明和注释,transform-flow-strip-types这个插件会直接将整个文件的内容干掉,导致里面的JSDoc丢失。
然后flow type声明转成JSDoc这个我刚开始就放弃了,要做到这点我需要一点点分析AST结构,将每种情况对应成JSDoc,很费力而且价值不是很大。