lodash實踐之依據規則處理物件
阿新 • • 發佈:2020-07-13
一.要解決的問題
在實際開發中,如果你遇到需要對一個比較複雜的物件刪除某些變數,或者替換某些變數的場景,你會怎麼處理呢?
比如:
const target = {
a: 1,
b: {
n: 2,
m: 3
},
c: 1,
d: {
n: 2,
m: 3
}
}
你需要對target物件做以下處理:
- 刪除a和b.n引數,並且b.m如果小於2的話進行刪除
- 替換c值和d.n值為9,並且替換d.m的值為(a+c)
你是否採用了下面的方式處理:
target.c = 9
target.d.n = 9
target.d.m = target.a + target.c
delete target.a
delete target.b.n
if (target.b.m < 2) delete target.b.m
如果規則簡單,可能用上面的方式就足夠了,但如果需要要處理的規則越來越多的話,這樣寫一是不好維護,別的童鞋可能要看半天,二是寫的時候也麻煩。那麼,如果把規則定義為一個變數,丟給一個工具函式來執行,這樣會不會比較直觀和好處理呢?
二.希望的使用方式
希望實現一個executor函式
/***
* executor 根據匹配規則處理物件的執行器
* @param targets 處理的物件列表
* @param rules 處理規則列表
* @return 規範化的物件列表
*/
const executor = (targets: Partial<object>[], rules: Record<string, Record<string, any>>): any[] => {}
然後書寫規則,執行該函式
const rules = {
delete: {
'[a,b.n]': true,
'b.m': (obj) => obj.b.m > 2
},
replace: {
'[c,d.n]': () => [9, 9],
'd.m': (obj) => obj.a + obj.c
}
}
const [result] = executor([target], rules)
console.log('處理結果:', result)
規則定義有兩種,一種的delete刪除,一種是replace替換。
- delete規則:
- key表示單變數如'b.m',也可以是用[]中括號括起來的多變數如'[a,b.n]'
- value值可以是布林值或者函式(引數為處理的物件),結果是true表示刪除,如果是[]中括號括起來的多變數,會批量處理。
- replace規則:
- key表示單變數如'd.m',也可以是用[]中括號括起來的多變數如'[c,d.n]'
- value值可以是某個型別值,或處理函式(引數為處理的物件,如果key為多變數,則需要返回多變數對應的值的陣列集合)
三.實現方法
首先,我們要寫一個函式,類似如可以將引數路徑'd.m'的值為v這個資訊,轉為一個物件型別{d: {m: v}}
/***
* converter 將字串引數路徑轉為物件
* @param path 字串形式的物件引數路徑
* @param val 引數的值
* @return 物件
*/
const converter = (path: string, val: any) => {
const keys = path.split(/\.|\[|\]/).filter(i => i)
const obj: Record<string, any> = {}
keys.reduce((pre: Record<string, any>, cur: string) => {
const index = keys.indexOf(cur)
pre[cur] = index === keys.length - 1 ? val : {}
if (!Object.keys(obj).length) {
obj[cur] = pre[cur]
}
return pre[cur]
}, {})
return obj
}
converter函式主要用reduce實現,converter用法是:
const obj = converter('d.m', 9)
console.log(obj)
// {
// d: {
// m: 9
// }
// }
接下來,重點來了:
import omit from 'lodash/omit'
import merge from 'lodash/merge'
/***
* executor 根據匹配規則處理物件的執行器
* @param targets 處理的物件列表
* @param rules 處理規則列表
* @return 規範化的物件列表
*/
const executor = (targets: Partial<object>[], rules: Record<string, Record<string, any>>): any[] => {
const isMultkeys: Function = (k: string): boolean => /^\[.+\,.+\]$/.test(k)
const isArray: Function = (r: any): boolean => Array.isArray(r)
const getMultkeys: Function = (k: string): string[] => k.replace(/^\[(.*)\]$/, ($1, $2) => $2).split(',')
const orders: string[] = ['replace', 'delete'] // 嚴格控制執行順序
return targets.map(target => {
orders.forEach((method: string) => {
const params: Record<string, any> = rules[method]
if (!params) return
switch (method) {
case 'delete':
const keys: string[] = Object.entries(params).reduce((p: string[], [k, v]: any) => {
v = typeof v === 'function' ? v(target) : v
if (!v) return p
if (isMultkeys(k)) {
let ks = getMultkeys(k)
ks =
(isArray(v) &&
v.reduce((r: string[], c: boolean, i: number) => {
c && r.push(ks[i])
return r
}, [])) ||
ks
p = p.concat(ks)
} else {
p.push(k)
}
return p
}, [])
target = omit(target, keys)
break
case 'replace':
target = Object.entries(params).reduce((p: Record<string, any>, [k, v]: any) => {
v = typeof v === 'function' ? v(target) : v
if (isMultkeys(k) && isArray(v)) {
const ks = getMultkeys(k)
const o = v.reduce((r: Record<string, any>, c: any, i: number) => {
r = merge(r, converter(ks[i], c))
return r
}, {})
p = merge(p, o)
} else {
p = merge(p, converter(k, v))
}
return p
}, target)
break
}
})
return target
})
}
有幾個實現注意點:
- delete和replace都需要對value進行型別判斷,如果是函式的話,取函式執行結果。replace還需要額外處理多變數的key-value情況。
- delete主要是用lodash/omit實現的,比較方便,omit可以這樣使用:
const object = { a: 1, b: {n: 9, m: 9} }; omit(object, ['a', 'b.n']); console.log(object) // { b: {m: 9} }
- replace主要是用lodash/merge實現的,merge好在合併多個物件的時候,如果引數是引用型別的,不會完全被覆蓋,如:
const result = merge({} , {b: {n: 1, m: 2}}, {b: {n: 9}}) console.log(result) // {b: {n: 9, m: 2}}
- replace處理key值為'd.m'的多路徑變數,就是先計算出值,然後使用剛剛實現的converter方法來轉化為物件,再將該物件merge到原來的target中。
- 執行順序需要控制先replace後delete。
四.總結
- 在實踐中,會發現lodash越來越多這好用的小函式,建議大家有空可以逛逛lodash的文件
- reduce函式也挺好用的,現在用上癮了
- 在處理實際問題的時候,可以多思考怎麼處理更好維護,別人一看就明白,定製使用感不錯的工具函式,造福大家,哈哈哈哈