最近遇到了个按需请求数据的需求,非常适合用于讲解闭包与链式设计的例子,故来分享一下思路。
大致需求如下: 目前有个 list, list 中每项 item 都是可展开的折叠项。当展开某个折叠项时,需要根据 item 的 code 另外去取 name 的映射。考虑到列表的数据量非常大,且一次性查询过多 code 时,接口的查询效率会明显降低,故采用按需请求映射的方案。
屏蔽与本例无关的属性,瘦身后的 list 数据结构大致如下:
1 2 3 4 5 6
| interface DataType { code: string; paymentTransaction: string[]; }
type ListType = DataType[];
|
我们知道大型企业中的数据会比较复杂,比较常见的一种情况是数据中有一个 id 或 code 是用于跟另一个数据项相关联的。学习过数据库的同学很容易就联想到了外键这个概念。
现在我们就要取出这些 code 发送给服务端去查询。考虑到 code 可能会有重复,因此可以将 codes 存入 Set
中,利用 Set
的特性去重。除此之外,为了使 name 映射可以被复用,每次从接口返回的 name 映射将会被缓存起来。若下次再触发事件时有对应的 key,便不再查询。
我们可以将这段逻辑抽离出来作为一个依赖收集的函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const mapping = new Map();
function collectionCodes(initCodes?: string[] | Set<string>) { const codes = new Set<string>(initCodes)
return { append(code: string) { if (!mapping.has(code)) { codes.add(code); }
return this; }, empty() { return !codes.size; }, value() { return codes; }, } }
|
collectionCodes
函数是用于收集 codes。它内部利用了闭包的特性将 codes 缓存了起来,并且在添加新的 code 之前会判断 code 在 local 的映射中是否已经存在。append
返回的 this
是经典的链式调用设计,允许多次链式添加。当本次依赖收集结束后,调用 value
方法获取最终的 codes。
可以写一些简单的 mock 数据进行尝试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function handleNameMapping(data: DataType) { const codes = collectionCodes() .append(data.code) .append('code-append-1') .append('code-append-1') .append('code-append-2');
data.paymentTransaction.forEach(code => codes.append(code));
if (codes.empty()) { console.log('can get values from existing mapping.') return; }
// 如果请求的数据需要转为数组,可以 Array.from 进行转换 const list = Array.from(codes.value()); console.log('fetch data before, codes --> ', list);
// mock 获取数据后拿到 name mapping 后,存入 mapping 中的行为. // 注意,Set 类型也可以用 forEach 方法,不一定得转为数组才可以操作 list.forEach(code => mapping.set(code, `random-name-${Math.random()}`)) }
const mockItemData = { code: 'code-main', paymentTransaction: [ 'code-payment-4', 'code-payment-1', 'code-payment-2', 'code-payment-1', 'code-payment-3', ] }
handleNameMapping(mockItemData); // fetch data before, codes --> (7) ["code-main", "code-append-1", "code-append-2", "code-payment-4", "code-payment-1", "code-payment-2", "code-payment-3"]
handleNameMapping(mockItemData); // can get values from existing mapping.
|
handleNameMapping
在发起请求前会做 code 收集,若本次收集中没有需要 fetch 的 code,那就避免发送无用的 HTTP 请求,从而达到了优化的目的。
最终示例的 TS 代码如下。若想直接在控制台尝试效果的话,可以通过 ts 官网中的 Playground 编译为可直接运行的 js 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| interface DataType { code: string; paymentTransaction: string[]; }
const mapping = new Map();
function collectionCodes(initCodes?: string[] | Set<string>) { const codes = new Set<string>(initCodes);
return { append(code: string) { if (!mapping.has(code)) { codes.add(code); }
return this; }, empty() { return !codes.size; }, value() { return codes; }, }; }
function handleNameMapping(data: DataType) { const codes = collectionCodes() .append(data.code) .append('code-append-1') .append('code-append-1') .append('code-append-2');
data.paymentTransaction.forEach((code) => codes.append(code));
if (codes.empty()) { console.log('can get values from existing mapping.'); return; }
// 如果请求的数据需要转为数组,可以 Array.from 进行转换 const list = Array.from(codes.value()); console.log('fetch data before, codes --> ', list);
// mock 获取数据后拿到 name mapping 后,存入 mapping 中的行为. // 注意,Set 类型也可以用 forEach 方法,不一定得转为数组才可以操作 list.forEach(code => mapping.set(code, `random-name-${Math.random()}`)) }
const mockItemData = { code: 'code-main', paymentTransaction: [ 'code-payment-4', 'code-payment-1', 'code-payment-2', 'code-payment-1', 'code-payment-3', ], };
handleNameMapping(mockItemData); // fetch data before, codes --> (7) ["code-main", "code-append-1", "code-append-2", "code-payment-4", "code-payment-1", "code-payment-2", "code-payment-3"]
handleNameMapping(mockItemData); // can get values from existing mapping.
|
本例的分析就到此结束了,虽然在本例中链式调用没有充分展示出自己的优势,但也可以作为一个设计思路用于参考。