先丟個你們都看過的阮一峯es6連接。最經常使用的方法:javascript
const obj = new Proxy(obj1, {
get(target, name){...},
set(target, name, newval){...},
})
複製代碼
相似Object.defineProperty
的set和get,攔截set和get操做進行一些其餘邏輯。可是proxy操做的是一個新的代理對象,是對原對象的一個代理。前端
最近作一個活動頁,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
突然想到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函數也就改個變量和加個noCourse
:api
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組件裏面有切換className
的classnames
庫(用react開發應該會接觸到:連接),而文案我這裏是用僞元素抓取的,因此也省去了if return
的代碼。後期不管活動要幹什麼,只要前面把props丟過來就是了,proxy會處理,最後返回一個數組。咱們只要在上一層組件加state甚至直接把cgi請求的結果都丟過來,下面一層proxy加邏輯,Course組件加樣式就能夠了。整個過程總的來講省了一些if以及render函數簡化,不過更復雜的狀況Course組件裏面仍是要寫if return
了。數組
另外一個例子:一個有點複雜的頁面,根據後臺返回的幾十個字段渲染一個列表。這裏咱們就看底部文案:已購買、已結束、xx人購買、xx時候開始等等,並且有不一樣樣式與Icon: bash
上一部分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>
複製代碼
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包,能兼顧深層對象,固然,只是個不穩定的版本
咱們在項目裏面,總會有一個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
。
最後,更復雜的狀況就自行發揮吧,總有方法讓你代碼更簡短和優雅