问题场景
组里的同事写了半个多小时递归代码,业务代码也耦合在里面,递归的执行直接依赖于业务上的数据,然后也不写注释,花了时间人力,业务数据改变之后,又要打开这个递归的方法进行修改。组里大部分同事都是在这样做,每次遇到一个需要递归的地方就写一次,有时候出错了又弄半天。有什么办法能解决这个问题呢?
能不能将递归的过程跟业务进行解耦,每次需要递归的时候,使用一个方法,然后注入数据,生成一个依赖该数据的并自动执行递归的方法,只需要往该方法传入想要的函数就能够递归地操作,或者查询呢
递归的实现和场景模拟
首先,让我们来实现一下递归方法,先手动地编写一个递归方法。
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是一个公共方法,不再需要组内成员去编写跟操作树形数据需要的递归过程了。
总结
- 我们将递归的过程进行了封装,产生了一个高阶函数wrapperProperty,这个高阶函数的作用就是产生一个用于操作树形数据的函数,而将递归的过程进行了隐藏。
- 好处是使用者不再关心递归如何编写,而只需要关心如何操作每一项数据,并且chilren属性名是什么。这大大减少了人力成本,以及可能出错的返工率。