JS Proxy的应用
熟悉vue3
的人都知道,它的数据响应式是用[JS Proxy](ES6 入门教程)实现的,proxy可以拦截诸如get、set、apply等很多操作,让我们可以通过“钩子”的方式,在这些操作时,做一些我们自己的动作。正好这段时间我们有一个项目应用到了它。
全局码表的使用
这个项目中定义了一些系统共用的常量,我们称为状态码,如下:
const CODE = {
goods: {
category: [
{code: 3088, label:'面膜'},
{code: 3089, label:'牙膏'},
]
},
order: {
status: [
{code: 10000, label: '待支付'},
{code: 10100, label: '待采购'},
{code: 10200, label: '待出库'},
]
}
}
刚开始时,它只有几个状态,随系统就直接下发了。可后来这些状态码越来越多,并且出现了“动态”状态码,也就是不同用户打开系统时,实时计算他的状态码,由异步接口下发。
组件的抽离
因为这个状态表在系统的各处都在使用,我们想到的第一个方案是抽离了出一个公共模块,应用层import 使用就行了。
import ajax from './util/ajax';
let codePromise;
export default async function getDynamicCode{
if(codePromise) return codePromise;
codePromise = new Promise(async (resolve)=>{
const codeMap = await ajax.get('/getDynamicCode');
resolve(codeMap);
});
return codePromise;
}
这样只要在业务逻辑里引用它就可以使用了,全局共享一份,不需要重复申请。
import getDynamicCode from './getDynamicCode';
async function showOrderStatus(statusCode) {
const CODE = await getDynamicCode();
const ret = CODE.order.status.filter(item=>item.code==statusCode);
return ret.length>0 ? ret[0].label : '-';
}
问题的产生
然后,随着业务的发展,大家对状态表的使用频率越来越高,问题也越来越多了。归纳了一下,有以下几个问题:
- 因为是动态码表,不同的人看到的码表不一样,有些code只对特殊角色开放,其它角色拿不到,使用的时候,因为码表没有值,就会报错。比如有些人是不发订单状态表的,操作它就会报错
CODE.order.status.filter(item=>item.code==statusCode);
为了解决它,就得每次使用前判断一下
let statusDesc = '';
if(CODE.order && CODE.order.status){
const ret = CODE.order.status.filter(item=>item.code==statusCode);
statusDesc = ret.length>0 ? ret[0].label : '-';
}
return statusDesc;
因为要到处写这个兼容逻辑就很麻烦。 2. 因为要配合组件使用,经常要把码表格式化成一定结构,传给组件,最常用的是选择列表和字典结构。
// options给下接选择组件使用
const options = CODE.order.status.map(({code:value, label})=>({value, label}));
// dict给表格组件使用
const dict = Object.fromEntries(CODE.order.status.map(({code, label})=>[code, label]));
Proxy的应用
为了解决以上两个问题,我们升级了getDynamicCode组件,使用到了Proxy技术。大致流程如下:
- 用Proxy包装CODE,它在“读”的操作加钩子,每次读取前,判断一下要读取的字段是不是存在;
- 如果“存在”目标属性,把对应的属性值使用Proxy封装后返回;“不存在”目标属性,把空数组用Proxy封装后返回
- 如果属性值是“options”或者”dict”,直接把当前对象按照Options和dict要求的结构格式化后返回。
最终代码如下:
import ajax from './util/ajax';
const proxySetting = {
get(target, propKey) {
const isUndefined = typeof target[propKey] === "undefined";
if (!isUndefined) {
return new Proxy(target[propKey], proxySetting);
}
const isArray = Array.isArray(target);
if (propKey === "options") {
if (isArray)
return target.map(({ code: value, desc: label }) => ({ value, label }));
else return new Proxy([], proxySetting);
}
if (propKey === "dict") {
if (isArray)
return Object.fromEntries(target.map(({ code, desc }) => [code, desc]));
else return new Proxy({}, proxySetting);
}
return new Proxy([], proxySetting);
},
};
let codePromise;
export default async function getDynamicCode{
if(codePromise) return codePromise;
codePromise = new Promise(async (resolve)=>{
const codeMap = await ajax.get('/getDynamicCode');
const codeProxy = new Proxy(codeMap, proxySetting);
resolve(codeProxy);
});
return codePromise;
}
这样就算读取了一个不存的属性,也不会报错,而是直接返回空数组。
const testCode = Code.category.bottle.glass;
虽然码表中没有它,但可以自由访问,不会触发js报错; 同时,可以直接访问options和dict属性,不用但心它们是否存在。
const columnsConfig = [
{title: '订单状态', dataIndex:'status', valueEnum: CODE.order.status.dict},
]
转载自:https://juejin.cn/post/7216621821961093176