vue directive 异步列表字典项格式化

106 阅读3分钟

很多时候写的字典项格式化方法都是同步的,一旦在表格、列表等地方进行异步字典项,就会出现一个接口请求N遍的情况。。

正常情况下,只要先请求字典项,再将列表中对应字段进行格式化就可以了。

但。。。这样不优雅。

能不能只请求一次,并且组件中不用加其他的处理过程呢?

想了好多办法, 最后 结合 异步请求( async/await ) 利用 directive 对表格、列表 等需求的异步字典项封装。

于是乎。。。有了下面神奇的东西。。

// 字典项缓存
const Dict = {};
// 字典项格式化缓存
const DictMapping = {};

// 这是一个队列的缓存
const DictQueueCache = {};
const DictQueueEvent = async function (type) {
    // 这是一个接口,用于请求字典项列表
    return await commonInputProps.GetDict({ type });
};

// 获取字典项逻辑封装
async function GetDict(type) {
    
    // 如果有字典,直接返回字典项列表
    if (type && Dict[type]) {
        return Dict.type;
    } else if (type && !Dict[type]) {
        // 如果对应字典没有被请求过,并且 队列缓存中也没有正在请求中的队列
        if (!DictQueueCache[type]) {
            // 请求字典项
            const _event = DictQueueEvent(type);
            // 在缓存中增加对应获取方法
            DictQueueCache[type] = _event;
            
            try {
                // 如果请求成功
                const res = await _event;
                Dict[type] = res;
                // 删除队列中的缓存对象
                delete DictQueueCache[type];
                return res;
            } catch (error) {
                // 失败也删除
                delete DictQueueCache[type];
                throw error
            }
        } else {
              return DictQueueCache[type]
        }
    }
}

// 将数组按照 value 的 key 值 格式化成 Mapping 对象的方法
// 用于处理列表很长的字典项,需要for循环很多遍的问题,提升效率
async function formatDictToMapping(options) {
    const type = options.type;
    const key = options.key || 'value';
    try {
        const res = await GetDict(type);
        const mapping = {};
        res.forEach(item => {
            mapping[item[key]] = item;
        });
        return mapping
    } catch (error) {
        console.error('格式化失败 =>', type, error)
    }
}

export default {
    install(app) {
        app.directive('dict', async (el, binding) => {
        
            // 此处需要接收 4个 参数
            // 有两种写法 
            
            // 写法一:
            // v-dict:type="1"   
            // 冒号后面 type 为字典项请求的名称。
            // 等号后面 1 字典项值。
            // 默认的字典项格式为 { label:'类型1', value:'1' }
            
            // 写法二:
            // v-dict:users="[12138, 'id', 'name']";
            // 冒号后面 users 为字典项请求的名称。
            // 数组中第一个参数为 列表中的值
            // 第二个参数为 值需要对应列表中哪一个字段
            // 第三个参数为 需要显示哪一个字段
            // 列表的格式为 { id: 12138, name: '用户名' }
            
            const type = binding.arg;
            const options = binding.value;
            let value, valueKey, showKey;
            if (Array.isArray(options)) {
                value = options[0];
                valueKey = options[1] || 'value';
                showKey = options[2] || 'label';
            } else {
                value = options;
                valueKey = 'value';
                showKey = 'label';
            };
            // 首先去 Mapping 缓存中 获取字典项格式化结果
            const CacheKey = `${type}_${valueKey}_${showKey}`;
            let _Dict = DictMapping[CacheKey]
            if (!_Dict) {
                // 如果没有缓存,去请求字典项缓存
                _Dict = DictMapping[CacheKey] = await formatDictToMapping({ type, key: valueKey })
            };
            // 获取显示名称
            const HtmlValue = _Dict[value][showKey];
            el.innerHTML = HtmlValue;
        })
    }
}


以下是简单讲解


// ........
这段代码 声明了一个异步请求方法
const DictQueueEvent = async function (type) {
    return await commonInputProps.GetDict({ type });
};
// ........
这段代码 创建了一个异步请求
const _event = DictQueueEvent(type);
//....... 


这就相当于  

const _event = new Promise(.....);

在列表中进行循环时,就变成了 

_event.then(......);
_event.then(......);
_event.then(......);
_event.then(......);
_event.then(......);
_event.then(......);
.......

当 _event 状态变化时,

列表的每一个对象都会接到相同的返回值进行处理。
并且 _event 只请求一次!
并且 根据 Mapping 对数据遍历进行了优化!
也就满足了异步列表格式化的需求啦!

注意 :正常的 _event.then(......).then(......).then(......).then(......) ... 
的形式 是顺次接收返回值进行返回,通过 return 传递给下一个 then 的。。。
其中有一个错误就会中断向下传递的过程。
所以不满足需求

完美!