JSDoc的进阶使用

前言


当 TypeScript 越来越流行时,许多项目选择使用 TypeScript 进行开发,因为它带来了许多好处,这一点大家都有所耳闻。

然而,一些旧项目仍然采用传统的 JavaScript 技术,因为进行全面重构成本太高。

在这种情况下,我们就需要介绍 JSDoc(也称为文档注释)作为一种解决方案。

JSDoc 是一种在 JavaScript 中使用注释来提供类型信息和文档说明的方法。 JSDoc 不仅可以提供类型提示,还可以生成文档,帮助开发人员更好地理解代码。

JSDoc 的出现为传统 JavaScript 项目引入了一种新的方式,使得即使在没有 TypeScript 的情况下,也能够获得一些类似 TypeScript 的好处。

重新看待JSDoc


相信大家都曾见过下面这样的写法,通过使用 @param 标签标记函数参数 的名称、类型和描述,使用 @returns 标签标记函数返回值

javascript 复制代码
/**
 * 对象合并,如果存在相同的属性,优先使用 target 的属性.
 */
function merge(source, target) { ... }

/**
 * 对象合并,如果存在相同的属性,优先使用 target 的属性.
 * @param { Object } source 源对象
 * @param { Object } target 目标对象
 * @returns { Object } 返回合并后的新对象
 */
function merge(source, target) { ... }

但如果是以下这样呢,是不是就有点TS的味道了(手动狗头)

javascript 复制代码
/**
 * 对象合并,如果存在相同的属性,优先使用 target 的属性
 * @template { Record<string, any> } T
 * @template { Record<string, any> } U
 * @param { T } source 源对象
 * @param { U } target 目标对象
 * @returns { Omit<T, keyof (T | U)> & U } 返回合并后的新对象
 */
function merge(source, target) { ... }

const test = merge({ name: 'test', gender: '男' }, { age: 18, gender: 1 })

可以在 JSDoc 中使用 TS 语法,这样的话,就算在 JS 开发的项目中,"体操哥"不也有发挥空间了嘛。

假设你已经具备了一定的TS水平,那接下来让我们直接进入正题。如果你不太了解甚至为泛型而感到头疼,《TS类型的进阶使用 - 掘金》这篇文章希望能够帮到你。

泛型


当谈到 TS 时,最具挑战性的部分莫过于泛型。泛型可以说是 TS 的精髓,它为我们提供了在编写灵活且类型安全的代码时所需的强大工具。泛型使得我们能够以一种抽象的方式来处理不同类型的数据,从而提高了代码的灵活性和可维护性。

在 JSDoc 中使用 @template 标签声明泛型

javascript 复制代码
/**
 * @template T 声明泛型
 * @param { T } value 入参
 * @returns { T } 返回值
 */
function identity(value) { } // function identity<T>(value: T): T

const a = identity("text"); // string
const b = identity(123); // number

可以从提示中看到 identity 已经变成了一个标准的泛型函数了,传入什么类型返回什么类型

使用逗号或多个标签声明多个泛型参数:

javascript 复制代码
/**
 * @template T,U,V
 * @template W,X
 */

还可以在泛型名称之前进行泛型约束

javascript 复制代码
/**
 * @template { unknown[] } T
 * @param { T } value 要获取长度的数组
 * @returns { T['length'] } 数组的长度
 */

可以看到这些全是数组的方法和属性,编辑器已经能够识别出你的 T 是一个数组了

最后,还可以为泛型设置默认类型

javascript 复制代码
/**
 * @template [T = string] 声明泛型并设置默认类型为string
 * @param { T } value 入参
 * @returns { T } 返回值
 */
function identity(value = '') { }

omit函数

结合之前所讲的泛型,来看看一个经典的函数------omit

javascript 复制代码
/**
 * omit函数 去除对象中的值得属性
 * @template { Record<string, any> } T 对象类型
 * @template { Array<keyof T> } K 元组类型
 * @param { T } target 目标元素
 * @param { K } keys 属性
 * @returns { Omit<T, K[number]> } 新对象
 */
function omit(target, keys) { ... }

效果:

总结:

  1. 能得到效果1,归功于 @template { Array<keyof T> } K 元组类型 这段代码将泛型 K 的类型约束成 (name | gender)[] ,使元组中的每个位置都是泛型 T 对象类型上的属性。
  2. 效果2则归功于多个泛型参数以及泛型约束,K[number] 将元组转成联合类型,假设函数的形参keys[name, gender]那么将转换为name | gender,所以图中的转换为 [name]=> name;然后再结合 Omit 即可达到我们想要的效果了。能够拿到 keys 具体的类型十分关键,想想如果只有一个泛型参数可以做到吗?

复杂类型


在前面,我们所讨论的类型中,都是一些简单的类型,一行代码就可以搞定的,如果某个函数的类型特别复杂,需要写很多代码,这时候又该如何应对呢?

类型导入

使用 import 从其他文件中导入类型

javascript 复制代码
// 文件:utils.d.ts

// 说明:过滤掉对象中属性值为指定内容的属性,使用 as 关键字来进行过滤
export type RemovePropertiesByValue<T extends Record<string, any>, V extends readonly unknown[]> = {
    [K in keyof T as T[K] extends V[number] ? never : K]: T[K]
}
javascript 复制代码
// 文件:JSDoc.js

/**
 * 去除对象中属性值为空的属性
 * @template { Record<string, any> } T
 * @template { readonly any[] } [V = [undefined, null]]
 * @param { T } target 要处理的对象
 * @param { V } values 自定义过滤的value值,默认为[undefined, null]
 * @returns { import("./utils").RemovePropertiesByValue<T, V> } 处理后的对象
 *            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 */
function filterObjectEmpty(target, values = [undefined, null]) { }
const test4 = filterObjectEmpty({ name: undefined, age: 18, gender: '', address: null }) // 测试代码

我们从 utils.d.ts 类型文件中使用import导入了 RemovePropertiesByValue 类型,此类型接收两个泛型参数。
@returns { import("./utils").RemovePropertiesByValue<T, V> } 导入了此类型并将 泛型T 和 泛型V 作为参数传递给 RemovePropertiesByValue

既然如此,还有什么类型是无法用 JSDoc 处理的呢,我想只有我们无法写的类型吧 本文的重点(泛型)已经讲完,如果你对 JSDoc 已经有了兴趣,可以去详细看看官方文档

参考资料


  1. Documentation - JSDoc Reference
  2. 块级标签 | JSDoc中文文档 | JSDoc中文网
相关推荐
Martin -Tang19 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发20 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端