最近遇到了個按需請求數據的需求,很是適合用於講解閉包與鏈式設計的例子,故來分享一下思路。git
大體需求以下: 目前有個 list, list 中每項 item 都是可展開的摺疊項。當展開某個摺疊項時,須要根據 item 的 code 另外去取 name 的映射。考慮到列表的數據量很是大,且一次性查詢過多 code 時,接口的查詢效率會明顯下降,故採用按需請求映射的方案。github
屏蔽與本例無關的屬性,瘦身後的 list 數據結構大體以下:typescript
interface DataType { code: string; paymentTransaction: string[]; } type ListType = DataType[];
咱們知道大型企業中的數據會比較複雜,比較常見的一種狀況是數據中有一個 id 或 code 是用於跟另外一個數據項相關聯的。學習過數據庫的同窗很容易就聯想到了外鍵這個概念。數據庫
如今咱們就要取出這些 code 發送給服務端去查詢。考慮到 code 可能會有重複,所以能夠將 codes 存入 Set
中,利用 Set
的特性去重。除此以外,爲了使 name 映射能夠被複用,每次從接口返回的 name 映射將會被緩存起來。若下次再觸發事件時有對應的 key,便再也不查詢。數組
咱們能夠將這段邏輯抽離出來做爲一個依賴收集的函數:緩存
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 數據進行嘗試:閉包
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 請求,從而達到了優化的目的。app
最終示例的 TS 代碼以下。若想直接在控制檯嘗試效果的話,能夠經過 ts 官網中的 Playground 編譯爲可直接運行的 js 代碼:dom
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.
本例的分析就到此結束了,雖然在本例中鏈式調用沒有充分展現出本身的優點,但也能夠做爲一個設計思路用於參考。