1. 程式人生 > 實用技巧 >lodash實踐之依據規則處理物件

lodash實踐之依據規則處理物件

一.要解決的問題

在實際開發中,如果你遇到需要對一個比較複雜的物件刪除某些變數,或者替換某些變數的場景,你會怎麼處理呢?

比如:

const target = {
    a: 1,
    b: {
        n: 2,
        m: 3
    },
    c: 1,
    d: {
        n: 2,
        m: 3
    }
}

你需要對target物件做以下處理:

  1. 刪除a和b.n引數,並且b.m如果小於2的話進行刪除
  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函式也挺好用的,現在用上癮了
  • 在處理實際問題的時候,可以多思考怎麼處理更好維護,別人一看就明白,定製使用感不錯的工具函式,造福大家,哈哈哈哈