名字隨便起——es6 Proxy

0. 前言

先丟個你們都看過的阮一峯es6連接。最經常使用的方法:javascript

const obj = new Proxy(obj1, {
  get(target, name){...},
  set(target, name, newval){...},
})
複製代碼

相似Object.defineProperty的set和get,攔截set和get操做進行一些其餘邏輯。可是proxy操做的是一個新的代理對象,是對原對象的一個代理。前端

1. 攔截展現結果

最近作一個活動頁,react全家桶。第一期沒什麼,在展現課程的時候,一個展現組件很常規的在render函數的過濾操做:java

this.props.courses.filter(...).map((course, idx) => {
        const { course_name: courseName, real_price: realPrice, course_id: courseId  } = course;
        const courseSetting = { courseName, realPrice, courseId, cashback, idx };
        return (
          <Course {...courseSetting} /> ); } 複製代碼

後來,第二期來了:活動id是2的要展現合輯,活動3要展現有效期的,活動4要...react

因而,幾種想法忽然浮現出來:es6

  • filter裏面寫各類if或者switch?太蠢了
  • 寫個map映射?可是可能不是每次都有明顯的規律或者簡單的過濾
  • 另外封裝一個函數,再if和其餘邏輯?仍是太常規了,若是後面的filter複雜到依賴其餘props呢?
  • 難道,就這樣了嗎,不能改變現狀了嗎,每天封裝各類函數寫if嗎?

突然想到Proxy,在constructor裏面作個代理:npm

this.display = new Proxy(this.props, {
      get(target, name) {
        if (name === 'courses') {
          if (props.aid === 2) { // 這裏就簡簡單單的filter
            const specialCourse = props.courses.filter(x => x.course_id === 0);
            if (specialCourse.length === 1) { // 理論上這裏是多餘判斷,可是能防止運營後臺錯誤配置
              return specialCourse;
            }
          } else if (props.aid === 3 && !props.courses) { // 省去了this.props.course && this.props.course.length判斷以及數組長度爲0的判斷
            return [{
              noCourse: true,
            }];
          }
        }
        return target[name];
      },
    });
複製代碼

2期咱們就展現那個id是0的合輯,3期咱們會在沒課程的時候展現一個新的卡片,而返回的仍是一個數組就不用if return了,咱們下面render函數也就改個變量和加個noCourseapi

this.display.courses.map((course, idx) => {
        const { course_name: courseName, real_price: realPrice, course_id: courseId, noCourse  } = course;
        const courseSetting = { courseName, realPrice, courseId, cashback, idx, noCourse };
        return (
          <Course {...courseSetting} /> ); } 複製代碼

後面在Course組件裏面有切換classNameclassnames庫(用react開發應該會接觸到:連接),而文案我這裏是用僞元素抓取的,因此也省去了if return的代碼。後期不管活動要幹什麼,只要前面把props丟過來就是了,proxy會處理,最後返回一個數組。咱們只要在上一層組件加state甚至直接把cgi請求的結果都丟過來,下面一層proxy加邏輯,Course組件加樣式就能夠了。整個過程總的來講省了一些if以及render函數簡化,不過更復雜的狀況Course組件裏面仍是要寫if return了。數組

另外一個例子:一個有點複雜的頁面,根據後臺返回的幾十個字段渲染一個列表。這裏咱們就看底部文案:已購買、已結束、xx人購買、xx時候開始等等,並且有不一樣樣式與Icon: bash

image
image
上一部分proxy裏面的switch分支代碼:

switch (name) {
          case 'hint':
            if (target.applied === 1) {
              return '已購買';
            }
            if (sb > now) {
              return `${formatDate('YYYY年MM月DD日 hh:mm', sb * 1000)}開售`;
            }
            if (!max) {
              return `${num}人已報名`;
            } else if (max === num) {
              return '已報滿';
            }
            if (now < se && se < now + 86400 * 3) {
              displayse = `,距停售還有${parseInt((se - now) / 86400, 10)}天`;
            }
            return `剩${max - num}個名額${displayse}`;
        }
複製代碼

最後,在jsx裏面只須要配合classnames這個工具就能夠切換樣式,而不一樣樣式有不一樣的僞元素,因此不管有多少種狀況,都不用大改jsx。app

<div
              className={cx(
                'hint',
                /開售/.test(hint) && 'no-start',
                /已購買/.test(hint) && 'purchase-ok'
                )}
            >
              {hint}
            </div>
複製代碼

2. 駝峯命名

cgi返回的字段老是下劃線,url不區分大小寫也老是下劃線,前端的js又是建議駝峯命名,不駝峯一個eslint就標紅。好比前面的代碼:

const { course } = this.props;
const { course_name: courseName, real_price: realPrice, course_id: courseId  } = course;
複製代碼

這個時候,就有一種指望:

const { course } = this.props;
const { courseName, realPrice, courseId  } = course;
複製代碼

很快,你們就想到了封裝一個函數深度遍歷對象改key再刪舊key。可是這是props啊,住手,你想幹啥?那就從新拷貝一份吧。從新搞個新的對象,是能夠達到目的,並且有不少這種思路又穩定在生產環境使用的包,不如咱們不從改變結果出發,直接從最開始的時候出發——get劫持name:

const destruction = new Proxy(obj, {
  get(target, name) {
    const _name_ = underscored(name); //駝峯轉下劃線
    if (_name_ !== name) {
      return target[_name_];
    }
    return target[name];
  },
});
複製代碼

而後咱們封裝一波就能夠了。固然,這隻能兼顧到一層對象,我基於proxy寫了一個npm包,能兼顧深層對象,固然,只是個不穩定的版本

3. 自定義cgi名字

咱們在項目裏面,總會有一個assets或者utils之類的文件夾,而後有一個專門放請求的js——好比api.js,裏面的代碼通常就是:

export function api1(args) {
  return request({
      url: `someurl`,
      method: 'GET',
      params: {
        ...args,
      },
    });
}

export function api2(args) {
  return request({
      url: `someurl`,
      method: 'POST',
      params: {
        ...args,
      },
    });
}
export function api3() {}
// ...
export function apin() {}
複製代碼

回頭看看本身的代碼,不少是直接簡單帶參數的get請求,並且命名通常也是根據接口下劃線風格的名字轉成駝峯命名的函數:

function isNewUser(args) {
  return request({
      url: `${root}/is_new_user`,
      method: 'GET',
      params: {
        ...args,
      },
    });
}

function getList(args) {
  return request({
      url: `${root}/get_list`,
      method: 'GET',
      params: {
        ...args,
      },
    });
}

function getRecord(args) {
  return request({
      url: `${root}/get_record`,
      method: 'GET',
      params: {
        ...args,
      },
    });
}
複製代碼

因而,咱們就這樣寫了一堆基本如出一轍的重複代碼,總感受很不舒服,此時proxy來了:

const simpleCGI = new Proxy({}, {
  get(target, name) {
    const _name_ = underscored(name);
    return (args) => request({
      url: `${config.rootCGI}/${_name_}`,
      ...defaultSetting, // 默認配置
      method: 'GET',
      params: {
        ...args,
      },
    });
  },
});
複製代碼

usage:

simpleCGI.getList({ aid: 1, forward: 'aasdasdasd'})
// 實際上和上面的getList同樣的效果
複製代碼

今後之後,不再用寫那麼多export了99%類似的function了。只要拿到simpleCGI這個對象,隨便你定義函數名字和傳入參數,你只須要留下的,也許就是一些霸氣而簡短的註釋

這太難看了吧,每次都是simpleCGI.xx而後再傳入一個對象

咱們再弄個配置表,能夠定義接口path也能夠取默認,也能夠給參數,這是最終效果:

/** * 極簡cgi列表配置,一次配置無需寫cgi函數 * @member <FunctionName>: <Setting> * @template Setting path | arguments (path: 可選,一般path和函數名轉下劃線後不同才配。arguments: 可選,按順序傳入準確的參數名用英文逗號隔開,參數用=給默認值) * @requires name Setting的path支持駝峯以及下劃線, FunctionName建議用駝峯否則eslint可能找你了 */
const CGI = {
  isNewUser: 'activity_id',
  getRecord: 'record|page=1,count=20,min_value=0',
  getPoster: 'get_exclusive_poster|activity_id, course_id',
  isSubscribe: 'isSubscribePubAccount|',
  getLessonList: 'activity_id, forward',
};

const CGIS = applyMapToSimpleCGI(CGI);

// 建議用commonjs規範的模塊方案
module.exports = {
  ...CGIS,
};
複製代碼

接下來咱們實現applyMapToSimpleCGI方法:

const applyMapToSimpleCGI = (map) => {
  const res = {};
  Object.keys(map).forEach(key => {
    map[key] = map[key].replace(' ', '');
    const exec = map[key].match(/\w+(?=\|)/);
    const _key = (exec && exec[0]) || key;
    const argNames = map[key].split('|').pop().split(',');
    res[key] = (...args) => {
      const obj = {};
      argNames.forEach((name, i) => {
        if (name) {
          const [_name, defaultValue] = name.split('=');
          obj[_name] = args[i] !== undefined ? args[i] : defaultValue;
        }
      });
      return simpleCGI[_key](obj);
    };
  });
  return res;
};
複製代碼

已經把CGIS暴露出去了,咱們用的時候能夠這樣:

export { isNewUser, getRecord } from 'assets/api';
複製代碼

前面爲何說不建議用export default呢,由於es6模塊是編譯時輸出接口,咱們寫好全部cgi請求函數在assets裏面,另一邊的某個組件的api.js引用的assets的部分函數時候不能直接用export from,須要這樣:

// 某個組件的api.js引用總的api裏面某些函數
 import __ from 'assets/api';
 const { isNewUser } = __;
 export { isNewUser };
複製代碼

用了es6模塊意味着寫了什麼就只能用什麼,而commonjs規範輸出一個取值的函數,調用的時候就能夠拿到變化的值。

今後,每次加接口,就在CGI對象加一行足夠了,或者不加直接用simpleCGI.function,代碼不用多寫,函數名字隨你定義,只須要註釋到位// xx接口: xxx,傳入xxx

最後,更復雜的狀況就自行發揮吧,總有方法讓你代碼更簡短和優雅

相關文章
相關標籤/搜索