reduce的实用用法

165 阅读4分钟

前言

reduce这个api大家一定不陌生,但具体怎么使用, 这里我将总结一下, Array对象其他api的很难做到, reduce可以轻松搞定的几种用途。之前潜水也看过几个介绍reduce的文章, 基本将reduce夸成神各种模拟Array对象的api, 其实没这个必要, 每个api都是术业有专攻, reduce是一把利器, 搭配递归主要针对于数据结构转化(扁平化转树形, 树形转扁平化)

扁平数据树形化

例子1:

// ['a', 'b', 'c', 'd'] =====> { 'a': { 'b': { 'c': { 'd': null } } } }

// reduceRight和reduce的区别就是遍历数组的起始位置是相反的
['a', 'b', 'c', 'd'].reduceRight((acc, cur) => ({ [cur]: acc }), null)

例子2:

//  需求:我们需要把订单列表根据不同部门的不同支付进行分组, 
//  且需要计算相应的金额

//  原始的扁平数据
const flatData =
 [{        
    projectId: ’2001‘, // 项目id
    price: 10,         // 项目费用
    payType: 'wxpay',  // 支付类型
    department: 'depA' // 部门 
  },
  “2002”: {
    projectId: ’2002‘,
    price: 40,
    payType: 'alipay',
    department: 'depB'
   },
   ...
 }]
 
// 处理后的树形数据
const result = 
   {
     depA: {
      wxpay: {
        list: [{             // A部门所有微信支付的订单列表 
           projectId: ’2001‘, 
           price: 10,
           payType: 'wxpay',
           department: 'depA'
         }],
         count: 10          // A部门微信收款的总数额
         },
       alipay: {}           // A部门支付宝收款的订单      
     },
     depB: {}               // B部门的订单分类
   }

const obj = flatData.reduce((acc, cur) => {
   const { payType, price, department } = cur
   return {
      ...acc,
      [department]: Reflect.has(acc, department)                     // 判断是已存在部门, 存在就累加, 不存在则新建
            ? {
                ...acc[department],                                  
                [payType]: Reflect.has(acc[department], [payType])   // 判断相同部门是否已存在支付类型
                  ? {
                      list: [...acc[department][payType].list, cur],
                      count: acc[department][payType].count + price,
                    }
                  : { list: [cur], count: price },
              }
            : {
                [payType]: {
                  list: [cur],
                  count: price,
                },
              },
        }
      }, {})
 
 

树形化数据扁平化

例子2:

// const obj = {                                  const obj = { 
//      a: {                                          a.b: 1,  
//      b: 1,                                         a.c: 2,
//      c: 2,                                         a.d.e: 5,
//      d: {                                          b[0]: 1, 
//         e: 5                      ======>          b[1]: 3,   
//        }                                           b[2].a: 2,
//      },                                            b[2].b: 3,  
//      b: [1, 3, { a: 2, b: 3 }],                    c:3
//      c: 3                                      }
// };

 const obj = {
        a: {
          b: 1,
          c: 2,
          d: { e: 5 },
        },
        b: [1, 3, { a: 2, b: 3 }],
        c: 3,
      }

const type = {
        '[object Array]': () => {},
        '[object Object]': () => {},
       }
 
 const verifyType = (param) => Object.prototype.toString.call(param)

 const combine = Object.keys(obj).reduce((acc, cur) => {
     if (!Reflect.has(type, verifyType(obj[cur]))) {
        return { ...acc, [cur]: obj[cur] }
     }

     let obj1 = {}
     if (verifyType(obj[cur]) === '[object Array]') {
         obj1 = obj[cur].reduce((acc1, cur1, index) => {
            if (!Reflect.has(type, verifyType(cur1))) {
              const key = `${cur}[${index}]`
              return { ...acc1, [key]: cur1 }
            } else if (verifyType(cur1) === '[object Object]') {
              return {
                ...acc1,
                ...Object.keys(cur1).reduce((acc2, cur2) => {
                  const key = `${cur}[${index}].${cur2}`
                  return { ...acc2, [key]: cur1[cur2] }
                }, {}),
              }
            }
          }, {})
        }

        if (verifyType(obj[cur]) === '[object Object]') {
           const originObj = obj[cur]
           const keys = Object.keys(originObj)
           
           const fun = (keys, obj, prefix = cur) => keys.reduce((acc3, cur3) => { 
            if (!Reflect.has(type, verifyType(obj[cur3]))) { 
                const key = `${prefix}.${cur3}`
                return {...acc3, [key]: obj[cur3] }
            } else if(verifyType(obj[cur3]) === '[object Object]') {
                return {...acc3, ...fun(Object.keys(obj[cur3]), obj[cur3], `${prefix}.${cur3}`)}
            }
            }, {})

          obj1 = fun(keys, originObj)  
        }

        return { ...acc, ...obj1 }
      }, {}) 
      
      console.log(combine)

拼装嵌套函数

reduce不仅能拼装数据类型, 也可以拼装函数,可以达到类似于JQuery链式调用的效果

 const func1 = x => x * 2
 const func2 = y => (y + 4) * 4
 let arr = [func1, func2] 

 // reduce会把前一个函数当作参数传入下一个函数中, 越后传入的函数越先执行
 const combineFunc = (...arg) => arr.reduce((a, b) => a(b(...arg)))
 combineFunc(2)  // 48
 
 arr = [func2, func1]
 combineFunc(2)  // 32

上述的例子大家可以知晓, 越先传入的函数越后执行, reduce会把前一个函数当作参数传入下一个函数中, 所以数组中每个函数必须有返回值不然没有意义了, 而且所有函数返回值不要求返回同一种数据类型(jQuery的方法处理的是同一个jQuery对象), 但是后面的函数一定要能处理前一个函数的返回值(e.g webpack中loader的顺序), 上述的例子所有, 假如各函数之间返回值的不是同一个目标对象的话有着严格的顺序要求, 说到此处, 大家是不是感觉有点似曾相识, 比如redux的applyMiddle, webpack中的loader, koa/express的中间件......

搭配proxy实现数据深度劫持

       const hijackedObj = { a: "hello", b: { c: ["78", { d: '100' }] } };

       const handler = {
        get: function (target, prop, receiver) {
          console.log("-------getter-----------", target, prop, receiver);
          return Reflect.get(target, prop, receiver);
        },
        set(target, property, value) {
          let result = Reflect.set(target, property, value);
          return result;
        },
      };

      const isType = (target) => Object.prototype.toString.call(target);
      const isObj = (target) => isType(target) === "[object Object]";
      const isArr = (target) => isType(target) === "[object Array]";

      const deep = (params) => {
        if (isObj(params)) {
          const obj = Object.keys(params).reduce((acc, cur) => {
            return {
              ...acc,
              [cur]:
                typeof params[cur] === "object"
                  ? deep(params[cur])
                  : params[cur],
            };
          }, {});
          return new Proxy(obj, handler);
        }

        if (isArr(params)) {
          const arr = params.map((item) =>
            typeof item === "object" ? deep(item) : item
          );
          return new Proxy(arr, handler)
        }
      };

     
      const result = deep(hijackedObj);
      result.b.c[1].d
      // -------getter----------- {a: 'hello', b: Proxy} b Proxy {a: 'hello', b: Proxy}
      // -------getter----------- {c: Proxy} c Proxy {c: Proxy}
      // -------getter----------- [78', Proxy] 1 Proxy {0: '78', 1: Proxy}
      // -------getter----------- {d: '100'} d Proxy {d: '100'}

有些同学可能认为的proxy本身就是深度监听函数, 其实不然, 观察一下上面控制台的信息就可以得知, 访问的当前对象属性及其嵌套的对象属性, 都会触发getter, 但是你不深层用proxy包裹的话, 无法精确判断出数据结构改动的地方, 所以一个对象有多少个引用类型就需要多少proxy对象。

结尾

以上就是我对reduce的理解与应用, 都是一些复制黏贴即可运行的代码, 写文章的目的一方面是为了给一些新手解决问题的思路, 更重要的记录自己的心路历程, 一些细枝末节不写出来太容易遗忘了😄