先丢个你们都看过的阮一峰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
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
。
最后,更复杂的状况就自行发挥吧,总有方法让你代码更简短和优雅