最近在做公司内部的一个的一个SDK的重构,这里总结一些经验分享给大家。

类型检查和智能提示

作为一个SDK,我们的目标是让使用者能够减少查看文档的时间,所以我们需要提供一些类型的检查和智能提示,一般我们的做法是提供JsDoc,大部分编辑器可以提供快捷生成JsDoc的方式,我们比较常用的vscode可以使用Document This。

另一种做法是使用Flow或者TypeScript,选择TypeScript的主要原因是自动生成的JsDoc比较原始,我们仍然需要在上面进行编辑,所以JsDoc维护和代码开发是脱离的,往往会出现代码更新了,JsDoc忘记更新的情况。

除此之外开发过程中我们无法享受到类型检查等对SDK开发比较重要的特性,TypeScript可以让我们减少犯错,减少调试的时间,另一方面这次开发的SDK在提供出去的时候就会进行一次相对简单的压缩,保证引入后的体积,所以会希望压缩掉JsDoc,而TypeScript可以通过在tsconfig.json中将declaration设置为true单独的d.ts文件。

一个带提示的SDK:

最后,对于开发同学来说,就算不使用TypeScript,也强烈建议使用vscode提供//@ts-check 注解,它会通过一些类型推导来检查你的代码的正确性,可以减少很多开发过程中的bug。

还有一个小技巧,如果你使用的库没有提供智能提示,你可以通过NPM/yarn的-D安装@types/{pkgname},这样你开发过程中就能够享受到vscode提供的智能提示,而-D安装到devDependencies中,也不会增加你在构建时的代码体积。

接口

既然提到了TypeScript,就提一下TypeScript的语法,基础类型没有必要赘述,而一些曾经的高级语法现在ES6也都能支持,这里提几点常用但是JavaScript开发者不太习惯使用的语法。

很多人在开始使用TypeScript的时候,会很迷恋使用any或者默认的any,推荐在开发中打开tsconfig中的strict和noImplicitAny来保证尽量少的any使用,要知道,滥用any就等于你的类型检查并没有实质效果。

对一些暂时不能确定内容的对象的类型,可以使用{[key: string]: any},而不要直接使用any,后期可以慢慢扩展这个接口直到完全消除any,同时TypeScript的类型支持继承,在开发过程中,可以拆解接口,利用组合继承的方式减少重复定义。

但是接口也会带来一个小痛点,目前vscode的智能提醒不能很好的对应到接口,当你输入到对应变量的时候,虽然会高亮,但是高亮的也只是一个定义了名字的接口。没有办法直接看到接口里定义了什么。但是当你输入了接口里面定义的key的部分时,vscode会给你完整key的提示。虽然这对开发过程中有一点不够友好,但是vscode开发团队表示这是他们故意设计的,所以在API参数上可以选择将一些必要(重要)参数用基础类型直接使用,而将一些配置放入一个定义为接口的对象中。

枚举

你有在代码中使用过:

 const Platform = {
ios: ,
android:
}

那你在TypeScript中就应该使用枚举:

 enum Platform {
ios,
android
}

这样在函数中你就可以为某个参数设置类型为number,然后传入Platform.ios这样,枚举可以增加代码的维护性,它可以利用智能提示保证你输入的正确,不再会出现魔数(magic number)。相对于对象,它保证了输入的类型(你定义的对象可能某一天不再只有number类型的value),不再需要额外的类型判断。

装饰器

对于装饰器其实很多开发者既熟悉又陌生,在redux,mobx比较流行的现在,在代码中出现装饰器的调用已经很普遍,但是大多数开发者并没有将自己代码逻辑抽成装饰器的习惯。

比如在这个SDK的开发中,我们需要提供一些facade来兼容不同的平台(iOS, Android或者Web),而这个facade会通过插件的形式让开发者自己注册,SDK会维护一个注入后的对象,常规的使用方法是到了使用函数后判断环境再判断对象中有没有想有的插件,有就使用插件。

实际来看,插件就是一个拦截器,我们只要阻止真正的函数运行就可以,大概的逻辑是这样的:

 export function facade(env: number) {
return function(
target: object,
name: string,
descriptor: TypedPropertyDescriptor<any>
) {
let originalMethod = descriptor.value;
let method; return {
...descriptor,
value(...args: any[]): any {
let [arg] = args;
let { param, success, failure, polyfill } = arg; // 这部分可以自定义
if ((method = polyfill[env])) {
method.use(param, success, failure);
return;
}
originalMethod.apply(this, args);
}
};
};
}

在SDK的开发过程中另一个常会遇到的就是很多参数的校验和再封装,我们也可以使用装饰器去完成:

 export function snakeParam(
target: object,
name: string,
descriptor: TypedPropertyDescriptor<any>
) {
let callback = descriptor.value!; return {
...descriptor,
value(...args: any[]): any {
let [arg, ...other] = args;
arg = convertObjectName(arg, ConvertNameMode.toSnake);
callback.apply(this, [arg, ...other]);
}
};

泛形

泛形可以根据用户的输入决定输出,最简单的例子是

 function identity<T>(arg: T): T {
return arg;
}

当然它没有什么特别的意义,但是它表明了返回是根据arg的类型,在一般开发过程中,你逃不开范型的是Promise或者前面的TypedPropertyDescriptor这种内建的需要类型输入的地方,不要草率的使用any,如果你的后端返回是一个标准结构体类似:

 export interface IRes {
status: number;
message: string;
data?: object;
}

那么你可以这样使用Promise:

 function example(): Promise<IRes> {
return new Promise ...
}

当然泛形有很多高级应用,例如泛形约束,泛型创建工厂函数,已经超出了本文的范围,可以去官方文档了解。

构建

如果你的构建工具是Webpack,在SDK的开发中,尽量使用node方式调用(即webpack.run执行),因为SDK的构建往往会应对很多不同的参数变化,node方式相比纯配置方式可以更加灵活的调整输入输出的参数,也可以考虑使用rollup,rollup的构建代码更加面向编程方式。

需要注意的是,在Webpack3和rollup中构建中可以使用ES6模块化的方式构建,这样业务代码引入你的SDK后,可以通过解构引入的方式减少最终业务代码的体积,如果你只是提供了commonjs的包,那么构建工具的tree sharking是无法生效的,如果使用babel的话注意关闭module的编译。

另外一种减少单个包体积的方式,可以使用lerna在一个git仓库里构建多个NPM包,比起拆仓库可以更方便的使用公共部分的代码,但是也需要注意对公共部分代码的修改不要影响到别的包。

其实对于大多数的SDK的来说,Webpack3和rollup使用感受是差不多的,比较常用的插件都有几乎同名的对应。不过rollup有两个优势,一个是rollup的构建更细化,rollup.rollup接受inputOptions生成bundle,还可以generate生成sourcemap,write生成output,在这个过程中我们可以做一些细致的工作。

第二点是rollup.rollup会返回一个promise,也就意味着我们可以使用async的方式来写构建代码,而webpack.run还是使用的回调函数,虽然开发者可以封装成promise,但是个人觉得还是rollup的写法还是更爽一点。

单元测试

上周我同事做了一个在线的分享,我发现很多同学都对单测很感兴趣也很疑惑,在前端开发中,对涉及UI的业务代码开发单测试比较困难的,但是对于SDK,单元测试肯定是准出的一个充要条件。当然其实我也很不喜欢写单测,因为单测往往比较枯燥,但是不写单测肯定会被老司机们“教育”的~_~。

一般的单测使用mocha作为测试框架,expect作为断言库,使用nyc提供单测报告,一个大概的单测如下:

 describe('xxx api test', function() {            // 注意如果要用this调用mocha,不要用箭头函数
this.timeout();
it('xxx', done => {
SDK.file
.chooseImage({
count: ,
cancel: () => {
console.log('选择图片取消----');
}
})
.then(res => {
console.dir(res);
expect(res).to.be.an('object');
expect(res).to.have.keys('ids');
expect(res.ids).to.be.an('array');
expect(res.ids).to.have.length.above();
uploadImg(res.ids);
done();
});
});
});

同样你可以用TypeScript写单测,当然在执行过程中,不需要再编译了,我们可以直接给mocha注册ts-node来直接执行,具体方式可以参考Write tests for TypeScript projects with mocha and chai — in TypeScript!。但是有一点需要提醒你,写单测的时候尽量依赖文档而不是智能提示,因为你的代码出错,可能会导致你的智能提示也是错误的,你根据错误的智能提示写的单测肯定也是。。。

对于网络请求的模拟可以使用nock这个库,需要在it之前增加一个beforeEach方法:

 describe('proxy', () => {
beforeEach(() => {
nock('http://test.com')
.post('/test1')
.delay()
.reply(, { // body
test1: ,
test2:
}, {
'server-id': 'test' // header
});
});
it(...
}

最后我们用一个npm script加上nyc在mocha前面,就可以获得我们的单测报告了。

这里我还提了几个TypeScript使用中的小tips给大家参考。

tips: 如何在非发包情况下给内部库添加声明

这个SDK在开发过程会依赖一个内部NPM包,为了让这个NPM支持TypeScript调用,我们有几种做法:

  • 给原包添加d.ts文件,然后发布.
  • 发布@types包,需要注意的是NPM不支持@types/@scope/{pkgname}这种写如果是私库包,可以使用@types/scope_{pkgname}这种写法.
  • 这次使用的标注一个文件夹存放对应的d.ts文件,这种方式适合开发中进行,如果你觉得你写的d.ts还不够完美,或者这个d.ts文件目前只有这个SDK有需要,可以这么使用,在tsconfig.json中修改:

     "baseUrl": "./",
    "paths": {
    "*": ["/type/*"]
    }

    办公资源网址导航 https://www.wode007.com

tips: 如何处理resolve和reject不同类型的promise回调

默认的reject返回的参数类型是any,不一定能满足我们的需要,这里给一个解决方案,并非最佳,作为抛砖引玉:

 
 interface IPromise<T, U> {
then<TResult1 = T, TResult2 = never>(
onfulfilled?:
| ((value: T) => TResult1 | PromiseLike<TResult1>)
| undefined
| null,
onrejected?:
| ((reason: U) => TResult2 | PromiseLike<TResult2>)
| undefined
| null
): IPromise<TResult1 , TResult2>;
catch<TResult = never>(
onrejected?:
| ((reason: U) => TResult | PromiseLike<TResult>)
| undefined
| null
): Promise<TResult>;

你不知道的前端SDK开发技巧的更多相关文章

  1. Kinect for Windows SDK开发入门(15):进阶指引 下

    Kinect for Windows SDK开发入门(十五):进阶指引 下 上一篇文章介绍了Kinect for Windows SDK进阶开发需要了解的一些内容,包括影像处理Coding4Fun K ...

  2. 新年之际,盘点一些APP开发技巧

    (原文:Reader Submissions - New Year's 2015 作者:Mattt Thompson 译者:培子 校对:蓝魂) 回顾过去一年发生在我们身边的事情时,有一点不得不提:对苹 ...

  3. WebApp开发技巧大全 看了就明白了

    [转载]阅读原文 自Iphone和Android这两个牛逼的手机操作系统发布以来,在互联网界从此就多了一个新的名词-WebApp(意为基于WEB形式的应用程 序,运行在高端的移动终端设备).开发者们都 ...

  4. 【转】几点 iOS 开发技巧

    [译] 几点 iOS 开发技巧 原文:iOS Programming Architecture and Design Guidelines 原文来自破船的分享 原文作者是开发界中知晓度相当高的 Mug ...

  5. 前端教程&开发模块化/规范化/工程化/优化&工具/调试&值得关注的博客/Git&面试-资源汇总

    内容精简 资源这么多,多看看多学习再总结肯定是好的.多读读就算看重了不算浪费时间,毕竟一千个读者就有一千个林黛玉,还有温故而知新,说不定多读一些内容,就发现惊喜了呢.不过,在此也精简一些内容,就1~2 ...

  6. JavaScript学习总结(六)——前端模块化开发

    早期的javascript版本没有块级作用域.没有类.没有包.也没有模块,这样会带来一些问题,如复用.依赖.冲突.代码组织混乱等,随着前端的膨胀,模块化显得非常迫切. 前端模块化规范如下: 一.前端模 ...

  7. 移动端web开发技巧(转)

    原文链接:http://liujinkai.com/2015/06/06/mobile-web-skill/ 移动端web开发技巧 这是一个最好的时代,因为我们站在潮流中:但也是一个最坏的时代,因为我 ...

  8. web前端工程师面试技巧 常见问题解答

    web前端工程师面试技巧 常见问题解答 每年的春招是各企业需求人才的黄金时期,不少的前端大牛或者前端新手在面试时候不知道怎么来回答面试官的问题,下面来看下我转载的这篇文章吧,希望对从事前端工作的你有所 ...

  9. 【全面解禁!真正的Expression Blend实战开发技巧】第七章 MVVM初体验-在DataGrid行末添加按钮

    原文:[全面解禁!真正的Expression Blend实战开发技巧]第七章 MVVM初体验-在DataGrid行末添加按钮 博客更新较慢,先向各位读者说声抱歉.这一节讲解的依然是开发中经常遇到的一种 ...

随机推荐

  1. java实现 洛谷 P1014 Cantor表

    题目描述 现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的.他是用下面这一张表来证明这一命题的: 1/1 1/2 1/3 1/4 1/5 - 2/1 2/2 2/3 2/4 - ...

  2. TZOJ 1214: 数据结构练习题――线性表操作

    描述 请你定义一个线性表,可以对表进行“在某个位置之前插入一个元素”.“删除某个位置的元素”.“清除所有元素”.“获取某个位置的元素”等操作.键盘输入一些命令,可以执行上述操作.本题中,线性表元素为整 ...

  3. (二)CRLF注入

    01 漏洞描述 在<HTTP | HTTP报文>一文中,我们介绍了HTTP报文的结构:状态行和首部中的每行以CRLF结束,首部与主体之间由一空行分隔.或者理解为首部最后一个字段有两个CRL ...

  4. 洛谷P1012 拼数 【题解】

    **原题链接** 题目描述 设有n个正整数(n ≤ 20),将它们联接成一排,组成一个最大的多位整数. 例如:n=3时,3个整数13,312,343联接成的最大整数为:34331213 又如:n=4时 ...

  5. 学习Redis好一阵了,我对它有了一些新的看法

    前言 本篇文章不是一篇具体的教程,我打算记录一下自己对Redis的一些思考.说来惭愧,我刚接触Redis的时候只是简单地使用了一下,背了一些面试题,就在简历上写下了Redis这个技能点. 我们能在网络 ...

  6. 微信小程序生命周期,事件

    目录 双线程模型 小程序中 app.js 中的生命周期 小程序的页面的生命周期 小程序的事件 双线程模型 像 Vue 的双向数据绑定 总结: 在渲染层将wxml文件与wxss文件转成js对象,也就是虚 ...

  7. Autoware 进行 Robosense-16 线雷达与 ZED 双目相机联合标定!

    项目要标定雷达和相机,这里记录下我标定过程,用的速腾 Robosense - 16 线雷达和 ZED 双目相机. 一.编译安装 Autoware-1.10.0 我没有安装最新版本的 Autoware, ...

  8. 让LED程序在片外SDRAM中运行

    让LED程序在片外SDRAM中运行 一.引子 在前一篇文章中,我们已经成功点亮过LED了,为什么还要再重复一次呢? 我们已经知道,Mini2440开发板有两种启动模式:从NorFlash启动和从Nan ...

  9. Java——选择、冒泡排序、折半查找

    //选择排序对数据进行升序排序 public static void selectSortArray(int[] arr){ for(int i = 0; i<arr.length-1;i++) ...

  10. Seafile的配置

    如果部署在apache或nginx后面,就是访问默认80或443.反向代理会帮你转到8000和8082上. 我使用443,我在路由器上设定了转发,比如 对外网12323这个端口会转到内网地址443上 ...