第一次在业务场景处理递归,并且用递归循环出组件,折腾了一下午成功的遍历了之后总结回顾一下。
什么是递归
递归是一个函数重复调用自己。
递归最普遍的应用就是阶乘,如下:
$$ n! = 12 34 5*…*(n-1)*n $$
代码实现如下:
1 2 3 4 5 6 7 8 function factorial (num ) { if (num <= 1 ) return 1 ; const val = num * factorial (num - 1 ); console .log (val); return val; }
程序运行的结果如下:
factorial
的调用模式为与输出结果相反,以num=5
为例,
1 2 3 4 5 6 7 8 9 10 factorial (5 ) === 5 * factorial (4 );factorial (4 ) === 4 * factorial (3 );factorial (3 ) === 3 * factorial (2 );factorial (2 ) === 2 * factorial (1 );factorial (1 ) === 1 ;
要理解为什么调用模式和控制台的输出正好相反,就必须要知道在factorial(num)
之中,在函数走到console.log(val);
之前,已经调用了factorial(num-1)
,一直到边界条件if (num <= 1)
时,才会返回值,走到下一步,在控制台打印出计算后的数值。
递归的边界条件 考虑一下,如果代码没有边界条件:
1 2 3 4 5 6 7 8 function factorial (num ) { const val = num * factorial (num - 1 ); console .log (val); return val; }
那么函数就会无止境的重复调用自己,陷入无限循环,num
的数值会从5
,4
,3
,2
,1
,0
,-1
,-2
一直递减,一直到调用栈内存不够:
1 2 3 4 5 6 7 8 9 10 11 RangeError: Maximum call stack size exceeded at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17) at factorial (factorial.js:4:17)
因此,所有的递归函数都需要至少一个边界条件。
React 递归生成组件 以一个 React 的代码文件夹结构为例子:
1 2 3 4 5 6 7 8 9 example |- public |- src |- css |- js |- components |- ui-components |- containers |- utils
代码文件夹的数据结构如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const treeData = [ { name: "example" , children: [ { name: "public" , children: [ ] , } , { name: "src" , children: [ { name: "css" , children: [ ] } , { name: "js" , children: [ { name: "components" , children: [ { name: "ui-components" , children: [ ] } ] } , { name: "containers" , children: [ ] , } , { name: "utils" , children: [ ] } ] } , ] } , ] } ]
在不知道文件架有几层的前提之下,想要遍历所有的组件无疑是一件非常困难的事情。就算知道数据的层数,一层层的循环迭代也会影响代码的可读性。鉴于所有的数据结构都是一致的,这种条件下就可以使用递归去循环遍历出结构。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 { tree (treeData, 0 ); } export const tree = (treeNodes, depth ) => { if (!treeNodes) { return null ; } return treeNodes.map ((node ) => { const nextDepth = depth + 1 ; return ( <React.Fragment key ={node.name} > <div style ={{ paddingLeft: depth * 10 }}> {node.name}</div > {tree(node.children, nextDepth)} </React.Fragment > ); }); };
产出效果如下:
1 2 3 4 5 6 7 8 9 example public src css js components ui-components containers utils
实际截图: 其他的用用包括不限于:
假设导航栏数据来自于其他的资源,可以通过其他的 UI 组件,如 bootstrap, antd, material-UI 等动态生成;
通过获取的数据生成列表;
搭建自己的 blog 平台时,如果想要生成更多的子目录的话,也可以用递归去生成文章的结构;
…