0. 前言
先丢个大家都看过的阮一峰 es6 链接 http://es6.ruanyifeng.com/#docs/proxy . 最常用的方法:
- const obj = new Proxy(obj1, {
- get(target, name){...},
- set(target, name, newval){...},
- })
类似 Object.defineProperty 的 set 和 get, 拦截 set 和 get 操作进行一些其他逻辑. 但是 proxy 操作的是一个新的代理对象, 是对原对象的一个代理.
下面讲的内容部分是基于 react 的, 对主体内容没什么影响, 只是提前说一下
1. 拦截展示结果
最近做一个活动页, react 全家桶. 第一期没什么, 在展示课程的时候, 一个展示组件很常规的在 render 函数的过滤操作:
- 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 要...
于是, 几种想法突然浮现出来:
filter 里面写各种 if 或者 switch? 太蠢了
写个 map 映射? 但是可能不是每次都有明显的规律或者简单的过滤
另外封装一个函数, 再 if 和其他逻辑? 还是太常规了, 如果后面的 filter 复杂到依赖其他 props 呢?
难道, 就这样了吗, 不能改变现状了吗, 天天封装各种函数写 if 吗?
忽然想到 Proxy, 在 constructor 里面做个代理:
- 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:
- 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 开发应该会接触到: 链接 https://www.npmjs.com/package/classnames ), 而文案我这里是用伪元素抓取的, 所以也省去了 if return 的代码. 后期无论活动要干什么, 只要前面把 props 丢过来就是了, proxy 会处理, 最后返回一个数组. 我们只要在上一层组件加 state 甚至直接把 CGI 请求的结果都丢过来, 下面一层 proxy 加逻辑, Course 组件加样式就可以了. 整个过程总的来说省了一些 if 以及 render 函数简化, 不过更复杂的情况 Course 组件里面还是要写 if return 了
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 包 https://www.npmjs.com/package/proxy-destruction , 能兼顾深层对象, 当然, 只是个不稳定的版本
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.
最后, 更复杂的情况就自行发挥吧, 总有方法让你代码更简短和优雅
来源: https://juejin.im/post/5beaf118f265da61620cf1df