ts引入公共方法_公共项目中,我用JSDoc代替了TS(为什么不用TS ?)

公共模块,通常会被多个项目、不同的开发人员使用,所以开发公共模块时,你自己会用还不够,要让所有人都能很快的知道怎么去使用,这一点很关键。通常会从3个方面做到这点:

精心分割代码逻辑,遵循开闭原则;

变量名采用自解释性的标识符;

依赖完善的使用提示。

本篇文章就是教你如何在实现js模块时,做好完善的使用提示。

是否要用ts写公共项目?

补充于:2020年11月17日09:43:12

公共模块使用ts编写的最佳做法是:在发布前通常会先编译成js,以及生成对应的types声明(d.ts文件),发布的源码只会包括这些js代码和types声明。

这样的话,就不会强迫业务项目也使用ts了,本节之前表达的观点“反对使用ts写公共模块”是错误的,当然 .vue文件这样的除外。

ts和JSDoc到底用哪个?

ts相对来说更严格,更严格的好处是可以更好的避免类型引起的潜在问题,坏处就是有时会给编码带来不便(比如它会强制你去声明每一个变量的类型,即使某些变量你不想声明类型时),jsDoc则相反。

ts在编译成js时类型信息会丢失,但js文件比较纯净,类型信息的保留是借助类型声明文件.d.ts的。jsDoc本身就是js,类型信息是通过注释完美的保存下来(当然引入了额外的类型相关的注释),无需借助额外的文件。

ts非常强大,提供了系列的配置方式来简化操作或增强/削弱类型相关的功能,但ts会引入额外的编译 导致项目稍重。

ts以及ts和vue的集成(国内市场vue老大)并不是每个人都会(国内感觉大多数都不会);JSDoc更易于学习,编码风格不像ts那样有变化(非常大的变化),更易于被团队成员接受。

经过一段时间的实践,我觉得两者在类型提示和校验方面都适应绝大部分场景,没有优劣之分,总体来说 ts更严格,更强大,JSDoc更灵活,更原生高效。

你的项目正常应该是打包成 umd包,并且和源码一起发布。因为:

umd包使用非常灵活,支持script标签隐式引用,amd,cmd,commonjs,esm引用,也是当前最流行的公共模块包。但umd包不能使用tree-shaking。

tree-shaking作为官方的项目优化方案,目前越来越流行,几乎已是必备的项目优化技术。tree-shaking是基于es module的,单纯的umd无法实现。

通常源码就是通过es module编写,所以将源码一起发布,不只是让使用者能够查看,还为了让使用者方便tree-shaking优化。

如果业务项目(这里指 引用你的公共项目模块的其他项目,以下都是这个意思)不会用到源码,只是引用umd包,那么采用ts编写源码不会有任何影响。

但如果要做tree-shaking而引用源码时,而你的源码是用ts编写,问题就来了:业务项目无形中被绑上了ts的“贼船”,引用你的源码,同时打包编译脚本要支持ts。

如果业务项目不想引入ts怎么办?

ts的很多优点,但的确不太适合开发公共项目,而且运用js Doc 注释,ts的绝大多数有点也能轻松实现。

而且即便业务项目使用ts开发,也能引用你的js模块。

为了不给业务项目带来副作用,就采用js语言吧。

js中使用ts

尽管确定使用 js语言编写公共项目,你依然享受到 ts的便利。ts天生就支持js!

// @ts-check

// js文件开头,使用上面这个注释,开启ts支持。接下来整个文件,都有了ts校验的功能,会出现类型错误提示等。

let a= 22

// @ts-ignore

a="string" // 将一个string赋给number类型的a,正常情况会有类型报错。但// 上一行 // @ts-ignore 这个注释表示,接下来的一条语句忽略ts校验,所以这里不报错

开启 ts 校验后,接下来就使用 jsDoc了,如果你还安装了typeScript:

#全局安装:

npm install -g typescript#或 项目局部安装:

npm install --save-dev typescript

那么接下来,使用命令 : tsc --declaration src/lib/service.js --allowJs  就可以根据你所写的 JSDoc,为你的service.js文件生成 .d.ts文件了。

转换的示例(下方示例的js代码生成的.d.ts):

export type MethodType = "get" | "post" | "delete" | "put" | "patch";

export type RequesFn= (method: MethodType, url: string, params: any) => Promise;

export type Person={

name: string;

age: number;

sex:'male' | 'female';

};

export type AxiosRes ={

code: number;

data: T;

msg: string;

};

export type TokenRes={

mediaId: string;

mediaKey: string;

token: string;

};

View Code

JSDoc示例

1、定义复杂类型+对变量添加类型

//@ts-check

/**

* @typedef {{name:string,age:number,sex:'male'|'female'}} Person*/

/**

* @type Person*/let jim= {name:'jim',age:28,sex:'female'}

把鼠标移到jim变量上,还有类型提示,就像ts中一样:

关于类型提示的示例截图,接下来就不贴上了,你可以在 vscode 试一下。

2、函数的类型

/**

* @typedef {'get'|'post'|'delete'|'put'|'patch'} MethodType*/

/**

* @returns { Promise} // 定义返回的类型

* @param {MethodType} method // 注意是@param,不再是@type了,MethodType是前面定义好的类型

* @param {string} url

* @param {*} [params] // 中括号表示可选参数,*表示any类型*/

functionmyAjax(method,url,params){return new Promise(resolve=>{//other code ...

})

}

js里也有类型推断,如果不指明返回的是 Promise  这里会自动推断出返回的是 Promise

你也可以这样定义函数类型:

/**

* @typedef {(method:MethodType,url:string,params:any)=>Promise} RequesFn*/

//或/**

* @typedef {{(method:MethodType,url:string,params:any):Promise}} RequesFn*/

//使用:

/**

* @type {RequesFn}*/

functionmyAjax(method, url, params) {

}

注意两种定义函数类型类型 RequestFn的方式,和 ts 是完全一样的!

3、定义和使用泛型

/**

* @template T // 这里用 @template声明泛型类型T

* @typedef {{code:number,data:T,msg:string}} AxiosRes*/

/**

* @typedef {{mediaId:string, mediaKey:string, token:string }} TokenRes

* @type {TokenRes|AxiosRes} // 这里是泛型的使用*/let res

鼠标放到res变量上:

记住这个res,它是个复合类型的变量,下一节的类型强转就用它了

//泛型示例2:/**

* @template T

* @param {T}

* @return {T}*/

function id(x){ return x }

/**

* @template {string} K // 泛型K 是string 或string的派生类型

* @template {{ serious(): string }} Seriousalizable // 泛型Seriousalizable 必须有一个serious方法

* @template T,U,V // 声明多个泛型*/

4、类型的强转(即ts中的类型断言)

if (/**@type {AxiosRes}*/(res).data ===undefined) {

console.log('res 是TokenRes类型')

let mediaId= /**@type{TokenRes}*/(res).mediaId

}else{

console.log('res 是AxiosRes类型')

let mediaId= /**@type {AxiosRes}*/(res).data.mediaId

}

把注释去掉,其实很简单:

if (res.data ===undefined) {

console.log('res 是TokenRes类型')

let mediaId=res.mediaId

}else{

console.log('res 是AxiosRes类型')

let mediaId=res.data.mediaId

}

类型断言的方法就是 @type 注释后面将要断言的变量用括号括起来   /**@type{TokenRes}*/(res)

5、引用第三方模块的类型

/**

* @param { import("axios").AxiosInstance } axios*/constructor(axios) {//这里可以轻松点出提示: axios.interceptors, axios.get

}

示例中构造器接收的参数axios,是一个 AxiosInstance类型,而这是来源于axios库定义好的一个类型!

当引入的第三方不是一个类型,而是一个变量怎么加上type呢?用 typeof :

/**

* @type {typeof import("./a").x }*/

var x = require("./a").x;

为了更友好的提示语可以做个中转:

/**

* @typedef {import("axios").AxiosInstance} AxiosInstance*/

/**

* @type {AxiosInstance}*/let axios

6、多行注释的方法

通常注释很长时,提示语会自动换行。但有时我们想让提示语按照定义好的规则进行换行显示,怎么办呢?单纯的在注释上换行没有作用,还需要在换行前面额外加一个* :

/**

* 七牛云上传服务:

** 大文件上传分片大小: 4M(4\*1024\*1024B)

** 上传路径已固定好为公司后台的路径,但需要提供axios(追加token,拦截代理到后台服务)

** 上传流程有3步:取上传token;上传;获取最终的文件key和url

* @param { import("axios").AxiosInstance } axiosInstance*/constructor(axiosInstance) {

提示语展示:

加上jsDoc的好处

回到最开始的话题,你的公共项目如何给使用者完善的使用提示?

当你使用jsDoc时,你会发现 业务项目如果引用你的源码,会自然的带上提示

如果是引用你的dist压缩包,那么我们合理的组织代码,然后用  tsc --declaration --allowJs xxx.js 命令,生成 .d.ts 发布出去,一样会有良好的提示。

你可能感兴趣的:(ts引入公共方法)