1. 程式人生 > 實用技巧 >怎樣讓 JS - API 具有更好的實用性

怎樣讓 JS - API 具有更好的實用性

1.前言

在上家公司開發後臺管理系統的時候,頻繁要處理各種資料顯示的問題,一開始是實現就好。後來寫多了,自己看得也難受了。就想著怎麼優化程式碼和複用了。下面就通過一個簡單的例子,怎麼讓 API 更加的實用,更好的複用。

1.程式碼的實用性,只能儘量,儘量再儘量。不會出現完美的API,或者是一次編寫,永不修改的 API 。

2.關於實用性,API 命名和擴充套件性也很重要。但之前寫過文章,在這裡就不重複了。[[前端開發]--分享個人習慣的命名方式](https://juejin.im/post/5b6ad6...,重構 - 設計API的擴充套件機制

2.舉個例子

比如有一個需求,有這樣的資料

{
    cashAmount: 236700,//回款金額(分)
    cashDate: "2018-05-26 10:25:28",//回款時間
    cashId: "SM2018022800020692",//回款ID
    cashStatus: 0,//回款狀態
    createTime: "2018-05-23 10:26:25",//建立時間
    custoName: "廣州測試有限公司",//回款公司名稱
    id: "SM2018022800020692",//回款ID
    merchandisers: "守候",//回款公司聯絡人
    ordId: "SO2018022800020692",//訂單ID
    payChannel: null,//支付方式
    remark: "",//備註
    userMobile: "18819222363",//回款公司聯絡人電話
}

需要對資料進行以下處理,再渲染到頁面

1.cashAmount 轉換成元,並保留兩位小數
2.cashStatus 進行解析(0-未回款 1-已回款)
3.payChannel 進行解析 ('zfb'-支付寶,'wx'-微信支付,'cash'-現金支付,'bankTransfer'-銀行轉賬)
4.所有值為 '' , null , undefined 的欄位,全部設定為:'--'

面對這樣的需要,很簡單,順手就來

let obj = {
    cashAmount: 236700,//回款金額(分)
    cashDate: "2018-05-26 10:25:28",//回款時間
    cashId: "SM2018022800020692",//回款ID
    cashStatus: 0,//回款狀態
    createTime: "2018-05-23 10:26:25",//建立時間
    custoName: "廣州測試有限公司",//回款公司名稱
    id: "SM2018022800020692",//回款ID
    merchandisers: "守候",//回款公司聯絡人
    ordId: "SO2018022800020692",//訂單ID
    payChannel: null,//支付方式
    remark: "",//備註
    userMobile: "13226452474",//回款公司聯絡人電話
}
function setValue(obj) {
    let _obj=jsON.parse(jsON.stringify(obj));
    //設定金額
    _obj.cashAmount = (_obj.cashAmount / 100).toFixed(2);
    //解析回款狀態
    _obj.cashStatus = _obj.cashStatus === 0 ? '未回款' : '已回款';
    //解析支付方式
    let payChannelLabel = {
        'zfb': '支付寶',
        'wx': '微信支付',
        'cash': '現金支付',
        'bankTransfer': '銀行轉賬'
    }
    _obj.payChannel=payChannelLabel[_obj.payChannel];
    //設定預設值
    for (let key in _obj){
        if(_obj[key]===''||_obj[key]===null||_obj[key]===undefined){
            _obj[key]='--'
        }
    }
    return _obj;
}
obj=setValue(obj);
console.log(obj)

結果也正確,但是如果以後需求變了,比如 userMobile 要改成 xxx xxx xxxx 這種展示方式呢?

也很簡單,修改下

function setValue(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    //設定金額
    //解析回款狀態
    //解析支付方式
    /*和上面程式碼一樣,不重複貼上*/
    //設定電話號碼格式
    let _formatType="xxx xxx xxxx",i = 0;
    _obj.userMobile= _formatType.replace(/x/g, function(){
        return _obj.userMobile[i++]
    });
    //設定預設值
    /*和上面程式碼一樣,不重複貼上*/
}

程式碼寫好了,想必大家也開始難受了。因為每改一次需求,就要改一次 setValue 。改的多了,出現問題的概率就大了。而且,這樣沒複用性。試想,如果別的頁面有一個需求,同樣的資料。但是 cashDate 欄位只需要精確到時分秒。這樣的需求,大同小異。但上面的程式碼不適用,需要拷貝一個 setValue 方法(就叫 setValue2 吧),然後新增 cashDate 只顯示 時分秒的邏輯。程式碼很好寫

function setValue2(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    //設定金額
    //解析回款狀態
    //解析支付方式
    //設定電話號碼格式
    /*和上面程式碼一樣,不重複貼上*/
    //設定 cashDate 只顯示時分秒
    _obj.cashDate= _obj.cashDate.split(' ')[0];
    //設定預設值
    /*和上面程式碼一樣,不重複貼上*/
}

3.單一職責原則

想必大家更難受了,因為沒發覆用,導致出現了幾乎完全一樣的函式。這個問題解決方式很多,先說下第一個,也是一個 API 設計原則--單一職責原則。

顧名思義,單一職責原則就是讓每一個函式只做一件事。下面把程式碼改造下

/**
 * @description 設定預設值
 * @param obj 待處理物件
 * @return obj 已處理物件
 */
function setDefault(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    for (let key in _obj){
        if(_obj[key]===''||_obj[key]===null||_obj[key]===undefined){
            _obj[key]='--'
        }
    }
    return _obj;
}
/**
 * @description 格式化電話號碼
 * @param obj 待處理物件
 * @return obj 已處理物件
 */
function setFormatMobile(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    let _formatType="xxx xxx xxxx",i = 0;
    _obj.userMobile= _formatType.replace(/x/g, function(){
        return _obj.userMobile[i++]
    });
    return _obj;
}
/**
 * @description 解析支付方式
 * @param obj 待處理物件
 * @return obj 已處理物件
 */
function setPayChannelLabel(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    let payChannelLabel = {
        'zfb': '支付寶',
        'wx': '微信支付',
        'cash': '現金支付',
        'bankTransfer': '銀行轉賬'
    }
    _obj.payChannel = payChannelLabel[_obj.payChannel];
    return _obj;
}
/**
 * @description 設定回款金額
 * @param obj 待處理物件
 * @return obj 已處理物件
 */
function setCashAmount(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    _obj.cashAmount = (_obj.cashAmount / 100).toFixed(2);
    return _obj;
}
/**
 * @description 解析回款狀態
 * @param obj 待處理物件
 * @return obj 已處理物件
 */
function setCashStatus(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    _obj.cashStatus = _obj.cashStatus === 0 ? '未回款' : '已回款';
    return _obj;
}

obj=setFormatMobile(obj);
obj=setCashStatus(obj);
obj=setCashAmount(obj);
obj=setPayChannelLabel(obj);
obj=setDefault(obj);

結果一樣,如果需要加上 cashDate 只顯示 時分秒。加上邏輯就行了

/**
 * @description 設定匯款時間
 * @param obj 待處理物件
 * @return obj 已處理物件
 */
function setCashDate(obj) {
    let _obj=JSON.parse(JSON.stringify(obj));
    _obj.cashDate = _obj.cashDate.split(' ')[0];
    return _obj;
}

obj=setFormatMobile(obj);
obj=setCashStatus(obj);
obj=setCashAmount(obj);
obj=setCashDate(obj);
obj=setPayChannelLabel(obj);
obj=setDefault(obj);
console.log(obj)

讓 API 保持單一原則的好處是,複用性比複雜的 API 更好,而且編寫的難度更低。

4.最少知識原則

上面的寫法雖然實現了複用,看著比之前好了一點,但是看著也是難受,畢竟賦值了幾次,而且還有那麼多的全域性函式。

首先,全域性函式這個容易解決,用一個物件包裹起來,全域性函式少了,也方便管理。

重複的程式碼和註釋,這裡忽略,不重複貼上
let handle={
   setDefault(obj) {
        //省略的程式碼
    },
    setFormatMobile(obj) {
        //省略的程式碼
    },
    setPayChannelLabel(obj) {
        //省略的程式碼
    },
    setCashAmount(obj) {
        //省略的程式碼
    },
    setCashStatus(obj) {
        //省略的程式碼
    }
}


obj=handle.setFormatMobile(obj);
obj=handle.setCashStatus(obj);
obj=handle.setCashAmount(obj);
obj=handle.setPayChannelLabel(obj);
obj=handle.setDefault(obj);
console.log(obj)

第二個讓人難受的地方就是一個步驟,經過了幾次的賦值,這個難免有點難受,寫起來也麻煩,記憶成本高。解決起來也很簡單,就是另寫一個函式,把那些操作步驟封裝在一起就行了。封裝的目的就是為了讓使用的人,只需要記住一個函式的使用方式就可以了,不需要記住多個函式的使用方式。

let handle={
   /*省略程式碼*/
   setCash(obj){
        let _obj=JSON.parse(JSON.stringify(obj));
        _obj=this.setFormatMobile(_obj);
        _obj=this.setCashStatus(_obj);
        _obj=this.setCashAmount(_obj);
        _obj=this.setPayChannelLabel(_obj);
        _obj=this.setDefault(_obj);
        return _obj;
    }
}
obj=handle.setCash(obj);
console.log(obj)

5.配置資料和業務邏輯分離

上面的程式碼,看著算是比較舒服了,但是問題還是有,就是 setCash 函式寫得太死了。固定了五個方法 :setFormatMobile,setCashStatus,setCashAmount,setPayChannelLabel,setDefault 。如果以後不需要處理電話號碼,又要改 setCash ,把 _obj=this.setFormatMobile(_obj); 這行程式碼去掉。雖然改動也很小,但是問題就出來了。如果其中一個地方需要執行 setFormatMobile ,就不能刪除。如果另一個地方, 不需要執行 setFormatMobile ,就要刪除。這樣子就顧此失彼了。

解決的方案想必大家也知道了,就是需要執行什麼函式,就在函式上動態傳入。

let handle={
   /*省略程式碼*/
   setCash(obj,fns='setFormatMobile,setCashStatus,setCashAmount,setPayChannelLabel,setDefault'){
        let _obj=JSON.parse(JSON.stringify(obj));
        let _fns=fns.split(',');
        _fns.forEach(item => {
            _obj=this[item](_obj);
        });
        return _obj;
    }
}
obj=handle.setCash(obj);
console.log(obj)

//比如另一個地方不需要執行 setFormatMobile
obj = {
    cashAmount: 236700,//回款金額(分)
    cashDate: "2018-05-26 10:25:28",//回款時間
    cashId: "SM2018022800020692",//回款ID
    cashStatus: 0,//回款狀態
    createTime: "2018-05-23 10:26:25",//建立時間
    custoName: "廣州測試有限公司",//回款公司名稱
    id: "SM2018022800020692",//回款ID
    merchandisers: "守候",//回款公司聯絡人
    ordId: "SO2018022800020692",//訂單ID
    payChannel: null,//支付方式
    remark: "",//備註
    userMobile: "13226452474",//回款公司聯絡人電話
}
obj=handle.setCash(obj,'setCashStatus,setCashAmount,setPayChannelLabel,setDefault');
console.log('比如另一個地方不需要執行 setFormatMobile',obj)

廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com

6.批量處理

看到這裡,好像差不多了。但是寫下去,大家才會知道,一般的後臺管理系統的使用者列表,資料一般不會只有一條。一般而言是一個數組物件。如下

let arr=[
    {
        cashAmount: 236700,//回款金額(分)
        cashDate: "2018-05-26 10:25:28",//回款時間
        cashId: "SM2018022800020692",//回款ID
        cashStatus: 0,//回款狀態
        createTime: "2018-05-23 10:26:25",//建立時間
        custoName: "廣州測試有限公司",//回款公司名稱
        id: "SM2018022800020692",//回款ID
        merchandisers: "守候",//回款公司聯絡人
        ordId: "SO2018022800020692",//訂單ID
        payChannel: null,//支付方式
        remark: "",//備註
        userMobile: "13226452474",//回款公司聯絡人電話
    },
    {/*省略的程式碼*/},
    {/*省略的程式碼*/},
    {/*省略的程式碼*/},
    //省略的程式碼
]

寫起來的時候呢,要這樣寫

arr.forEach((item,index)=>{
    arr[index]=handle.setCash(item);
})
console.log(arr)

雖然程式碼不多,但是有更好的方案,就用更好的方案。比如使用批量處理的方式。就多寫一個函式就行了。

let handle={
   /*省略程式碼*/
   batch(arr,fns,...orther){
        let _arr=JSON.parse(JSON.stringify(arr));
        let _fns=fns.split(',');
        _arr.forEach((item,index)=>{
            _fns.forEach(fn => {
                _arr[index]=this[fn](_arr[index],...orther);
            });
        })
        return _arr
    }
}

呼叫的時候就比之前簡單了一點,結果也正確

arr=handle.batch(arr,'setCash')
console.log(arr)

要傳其他引數也可以

arr=handle.batch(arr,'setCash','setCashStatus,setCashAmount,setPayChannelLabel,setDefault')
console.log(arr)

如果要傳入多個操作函式

arr=handle.batch(arr,'setCashStatus,setCashAmount')
console.log(arr)

7.小結

關於開發上,API 的實用性,暫時就先提這幾個方面,如果以後發現有其他例子,還能從其他方面提高 API 的實用性,就再發文章分享。