当前位置: 首页 > 科技观察

前端开发工具Jsdoc:让我们像Typescript一样写JavaScript

时间:2023-03-13 11:58:43 科技观察

众所周知,由于JS的语言特性,没有开发工具能够为JS提供足够好的智能提示。正因如此,微软造的轮子:typescript,横空出世!那么,有没有不用TypeScript的解决方案呢?没错,就是今天的主角:jsdoc;这可能是大家很少用到的开发工具;它是一个允许你像typescript一样编写JavaScript的工具;是的,jsdoc主要是用来给js添加类型信息的。下面我们来看一个简单的函数。这个函数接收一个参数modalElement。由于编辑器不知道它是什么类型,所以在调用它的querySelector方法时无法得到编辑器的智能提示;同样,编辑器也不能对btnClose的click方法给出intellisense。constcloseModal=modalElement=>{if(modalElement){constbtnClose=modalElement.querySelector('.el-dialog__close')if(btnClose){btnClose.click()returntrue}}}这个时候就到了jsdoc出现;它的语法需要写在多行注释中,因为它不是js语法的一部分;我们只需要给modalElement和btnClose加上类型注解,编辑器就知道它们是什么类型,有什么能力;为函数参数指定类型,使用@param标记,语法:@param{type}参数名;指定变量的类型,使用@type标记,语法:@type{type};代码如下:/**@param{HTMLDivElement}modalElement*/constcloseModal=modalElement=>{if(modalElement){/**@type{HTMLLinkElement}*/constbtnClose=modalElement.querySelector('.el-dialog__close')if(btnClose){btnClose.click()returntrue}}}现在,当我们将指针移动到modalElement时,它不是简单的任何类型。编辑器可以根据文档注释判断是HTMLDivElement类型,我们可以在调用它的querySelector方法时得到编辑器的智能提示;当我们把指针移到click上时,editor告诉我们这个方法继承自HTMLElement。@returns注解用于指定函数返回值的数据类型;语法:@returns{type};下面的函数返回一个由HTMLDivElement组成的数组。/**@returns{HTMLDivElement[]}*/constgetAllModals=()=>{returnArray.from(document.body.querySelectorAll('.el-dialog__wrapper,.el-drawer__wrapper')).filter(_=>{returnwindow.getComputedStyle(_,null).display!=='none'})}如下图,当我们调用getAllModals函数返回值的reduce方法时,编辑器可以进行智能提示.我们也可以使用@typedef来标记自定义类型;语法:import('modulepath'),用于从模块导入TS类型定义;&符号用于合并2种类型;下面的例子定义了一个名为RouteConfig的类型,该类型在import('vue-router').RouteConfig的基础上,在meta字段上增加了一个类型为number的索引字段。/***@typedef{import('vue-router').RouteConfig&{meta:{index:number}}}RouteConfig*/下面的例子定义了一个EntAccountInfo类型,它包含2个字段:id和numeric类型的character字符串类型的密码。/***@typedef{{id:number,password:string}}EntAccountInfo*/如果我们有更多的字段,我们可以使用@property标签来定义每个字段。/***@typedefUserData用户数据*@property{any}entid租户id*@property{string}namename*@property{string}workNo工作号*@property{string}userId用户id*@property{string}username登录用户名*/我们自定义的类型和内置类型使用完全一样;请看下面的例子,包括我们上面创建的内置类型和自定义类型;当我们为对象字段指定类型时,有两种写法:写在字段名的前面或上面;你认为哪种风格优雅?exportconststate=Vue.observable({/**@type{string[]}*/keeps:[],/**@type{RouteConfig[]}*/menus:[],/**@type{EntAccountInfo}*/entInfo:{},/**@type{UserData}*/userData:{},})exportconststate=Vue.observable({/**@type{string[]}*/keeps:[],/**@type{RouteConfig[]}*/menus:[],/**@type{EntAccountInfo}*/entInfo:{},/**@type{UserData}*/userData:{}})我们您可以使用竖线字符为一个变量指定多种可能的类型。请看下面的例子。当用户调用该函数时,编辑器会提示该函数希望接收一个类型为date或string或value的参数time。/**@param{日期|字符串|number}time*/exportconstgetHalfYearAfterTime=time=>{constdate=newDate(time)date.setMonth(date.getMonth()+6)date.setDate(date.getDate()-1)returndate}如果我们函数有无限个参数,我们可以使用语法:@param{...type}参数名,指定参数类型;看下面的例子:/**@param{...string}paths*/exportconstgetApiUrl=(...paths)=>joinPath(API_BASE,...paths)是一个具有无限参数和更高级类型的函数注释;请看下面的代码,formRequest是axios的一个实例,我们每次发起post请求都希望写在10个字符以内,定义一个post函数,直接将post方法调用返回给formRequest。exportconstpost=(...args)=>formRequest.post(...args)通过小编的提示,我们知道axios的post方法有3种不同类型的参数;而我们为了省事使用了扩展运算符,无论传入多少参数,仍然全部交给axios实例的post方法;那么,参数类型应该如何标注呢?我们可以使用括号语法来指定每个参数的字段名和类型,例如:/**@param{[url:string,data?:any,config?:RequestConfig]}args*/exportconstpost=(...args)=>formRequest.post(...args)下图是指针移入post函数时小编给出的提示;是不是很酷?下面是RequestConfig的类型定义。我们扩展了AxiosRequestConfig并向其添加了2个布尔字段;现在当我们调用post函数时,编辑器就会知道我们的第三个参数config包含了这2个布尔字段。/***@typedef{import('axios').AxiosRequestConfig&{needAuth:boolean,saveToken:boolean}}RequestConfig*/jsdoc也可以定义泛型类型,语法:@template泛型名称;看下面的例子,TreeNode是一个泛型类型,唯一可以确定的是它有一个children字段;它包含的其他字段由泛型类型T决定。/***@templateT*@typedef{T&{children:TreeNode[]}}TreeNode*/我们也可以在函数类型注释中使用泛型,请参阅下面的例子,我们定义了一个泛型T,参数data是一个泛型T数组,返回值是一个泛型类型TreeNode的数组。/***@templateT*@param{T[]}data*@param{{key:string,parentId:string}}config*@returns{TreeNode[]}*/exportconsttoTree=(data,config)=>{const{key='id',parentId:pId='parentId'}=config||{}constids=data.map(_=>_[key])/**@type{TreeNode[]}*/constresult=[]//......returnresult}以上是我工作中最常用的jsdoc用法,还有很多用法没有涉及;篇幅有限,可以去官网查看文档;希望本文能帮助大家提高JS技术,感谢阅读!