高阶函数封装递归方法,处理树形数据

173 阅读4分钟

问题场景

组里的同事写了半个多小时递归代码,业务代码也耦合在里面,递归的执行直接依赖于业务上的数据,然后也不写注释,花了时间人力,业务数据改变之后,又要打开这个递归的方法进行修改。组里大部分同事都是在这样做,每次遇到一个需要递归的地方就写一次,有时候出错了又弄半天。有什么办法能解决这个问题呢?

能不能将递归的过程跟业务进行解耦,每次需要递归的时候,使用一个方法,然后注入数据,生成一个依赖该数据的并自动执行递归的方法,只需要往该方法传入想要的函数就能够递归地操作,或者查询呢

递归的实现和场景模拟

首先,让我们来实现一下递归方法,先手动地编写一个递归方法。

 const handleProperty = (list, pItem) => {
    return list
      .map((item)=>{
      // 假设每次递归的时候都要对数据进行修改,这个修改的函数是excuteAction,通常情况下这个函数
      // 都是根据业务来的
      // 修改完之后的数据是result
        const result = excuteAction(item,pItem);
        // 如果有children属性,并且children属性的长度大于0,也就是说该层次的节点数量大于0
        const hasChild = result.hasOwnProperty('children')
        if(hasChild && result.children.length > 0) {
              const childs = result.children;
              // 那么则依次对该层次的节点进行处理,当下一层节点没有children属性需要处理时,
              // 则将下一层的所有处理的结果放回到children属性,也就是更新
              result.children = handleProperty(childs, result);
        }
        // 直到没有children属性要处理,那么则返回处理之后的结果
        return result;
      })
     .filter(Boolean);
 };
      

接下来我们传递传入list,也假定excuteAction是以下函数。

const tree = {
            testProp:'',
            children:[{
                testProp:'',
                children: [{
                    testProp:''
                }]
            },{ 
                testProp:'',
                children:[{
                    testProp:'',
                }]
            }]
}

const excuteAction = (item,pItem)=>{
    item.testProp = 'aaaa'
    return item;
}

// 结果:result跟tree是差不多的数据,就是所有的testProp属性都变成了aaaa
const result = handleProperty([tree])

目标

重点不是递归如何写,而是如何避免重复写递归。

思考

想想,你的业务很多个地方需要对树形数据进行处理,总不能一直写递归,组里各个成员写的递归可能还不太一样,到时候看别人代码也难免要花上时间。那么怎么避免重复写递归呢?

如果观察一下,我们能发现其实excuteAction就是分离出来的一个操作,每次业务不同,处理的树形数据不同,要求处理之后的数据就不同,那么我们就把变化的部分拿出来了,剩下的handleProperty方法其实就是公共的递归过程的部分。当然别忘了children也可能是变化的字段

明白了这一点之后,我们只需要稍微改一下handleProperty函数

解决方案

// childrenProp修改为可以配置的属性,cb就是修改数据的方法,根据情况的不同进行自定义。
const wrapperProperty = (cb:any, prop={ childrenProp: 'children'})=>{
if(!cb && cb instanceof Function){
    throw new Error('cb isn't function')
}
const { childrenProp } = prop;
 const handleProperty = (list, pItem?:any) => {
    return list
      .map((item)=>{
        const result = cb(item,pItem);
        const hasChild = result.hasOwnProperty(childrenProp)
        if(hasChild && result[childrenProp].length > 0) {
              const childs = result[childrenProp];
              result[childrenProp] = handleProperty(childs, result);
        }
        return result;
      })
     .filter(Boolean);
   };
 return handleProperty;
}

使用方法

依然使用刚刚的数据来进行模拟场景


const tree = {
            testProp:'',
            children:[{
                testProp:'',
                children: [{
                    testProp:''
                }]
            },{ 
                testProp:'',
                children:[{
                    testProp:'',
                }]
            }]
}

const excuteAction = (item,pItem)=>{
    item.testProp = 'aaaa'
    return item;
}

const excuteTree = wrapperProperty(excuteAction, { childrenProp: 'children' });
const result = excuteTree([tree])

result跟上面的result的数据是一致的。也就是这个改造对结果没有影响。同时wrapperProperty是一个公共方法,不再需要组内成员去编写跟操作树形数据需要的递归过程了。

总结

  1. 我们将递归的过程进行了封装,产生了一个高阶函数wrapperProperty,这个高阶函数的作用就是产生一个用于操作树形数据的函数,而将递归的过程进行了隐藏。
  2. 好处是使用者不再关心递归如何编写,而只需要关心如何操作每一项数据,并且chilren属性名是什么。这大大减少了人力成本,以及可能出错的返工率。