你不知道的前端SDK開發技巧

類型檢查和智能提示前端

做爲一個SDK,咱們的目標是讓使用者可以減小查看文檔的時間,因此咱們須要提供一些類型的檢查和智能提示,通常咱們的作法是提供JsDoc,大部分編輯器能夠提供快捷生成JsDoc的方式,咱們比較經常使用的vscode可使用Document Thisnode

另外一種作法是使用Flow或者TypeScript,選擇TypeScript的主要緣由是自動生成的JsDoc比較原始,咱們仍然須要在上面進行編輯,因此JsDoc維護和代碼開發是脫離的,每每會出現代碼更新了,JsDoc忘記更新的狀況。android

除此以外開發過程當中咱們沒法享受到類型檢查等對SDK開發比較重要的特性,TypeScript可讓咱們減小犯錯,減小調試的時間,另外一方面此次開發的SDK在提供出去的時候就會進行一次相對簡單的壓縮,保證引入後的體積,因此會但願壓縮掉JsDoc,而TypeScript能夠經過在tsconfig.json中將declaration設置爲true單獨的d.ts文件。webpack

一個帶提示的SDK:ios

最後,對於開發來講,就算不使用TypeScript,也強烈建議使用vscode提供//@ts-check 註解,它會經過一些類型推導來檢查你的代碼的正確性,能夠減小不少開發過程當中的bug。

還有一個小技巧,若是你使用的庫沒有提供智能提示,你能夠經過NPM/yarn的-D安裝@types/{pkgname},這樣你開發過程當中就可以享受到vscode提供的智能提示,而-D安裝到devDependencies中,也不會增長你在構建時的代碼體積。git

接口web

既然提到了TypeScript,就提一下TypeScript的語法,基礎類型沒有必要贅述,而一些曾經的高級語法如今ES6也都能支持,這裏提幾點經常使用可是JavaScript開發者不太習慣使用的語法。npm

不少人在開始使用TypeScript的時候,會很迷戀使用any或者默認的any,推薦在開發中打開tsconfig中的strict和noImplicitAny來保證儘可能少的any使用,要知道,濫用any就等於你的類型檢查並無實質效果。編程

對一些暫時不能肯定內容的對象的類型,可使用{[key: string]: any},而不要直接使用any,後期能夠慢慢擴展這個接口直到徹底消除any,同時TypeScript的類型支持繼承,在開發過程當中,能夠拆解接口,利用組合繼承的方式減小重複定義。json

可是接口也會帶來一個小痛點,目前vscode的智能提醒不能很好的對應到接口,當你輸入到對應變量的時候,雖然會高亮,可是高亮的也只是一個定義了名字的接口。沒有辦法直接看到接口裏定義了什麼。可是當你輸入了接口裏面定義的key的部分時,vscode會給你完整key的提示。雖然這對開發過程當中有一點不夠友好,可是vscode開發團隊表示這是他們故意設計的,因此在API參數上能夠選擇將一些必要(重要)參數用基礎類型直接使用,而將一些配置放入一個定義爲接口的對象中。

枚舉

你有在代碼中使用過:

const Platform = {
  ios: 0,
   android: 1
}
複製代碼

那你在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(6000);
 it('xxx', done => {
 SDK.file
  .chooseImage({
    count: 10,
    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(0);
    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(200)
  .reply(200, {            // body
    test1: 1,
    test2: 2
  }, {
    '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/*"]
}
複製代碼

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>; 
複製代碼
相關文章
相關標籤/搜索