递归遍历多级分类树生成HTML:方案选型与重复bug排查咨询
分类树遍历生成HTML字符串问题
需求
实现一个函数,遍历最多支持6级下拉的分类树,返回包含分类链接及子分类的HTML字符串。
问题
- 递归函数是否为该场景的最佳方案?若不是,请说明原因并给出替代方案;
- 当前代码遍历第一个子分类时可生成预期HTML结构(如下):
ul class="child first"><li class="parent"><a class="arrow-right" href="#">(parent name in the list)</a><ul class='child second'><li class="parent"><a class="arrow-right" href="#">(the name of the first sub child in parent)</a><ul class='child third'><li class="parent"><a href="#">last child of the previous sub child </a></li>
但遍历第二个子分类时会出现之前的子分类内容重复,示例如下:
ul class="child first"><li class="parent"><a class="arrow-right" href="#">(parent name in the list)</a><ul class='child second'><li class="parent"><a class="arrow-right" href="#">(the name of the first sub child in parent)</a><ul class='child third'>ul class="child first"><li class="parent"><a class="arrow-right" href="#">(parent name in the list)</a><ul class='child second'><li class="parent"><a class="arrow-right" href="#">(the name of the first sub child in parent)</a><ul class='child third'><li class="parent"><a href="#">last child of the previous sub child </a></li> <ul class='child second'><li class="parent"><a class="arrow-right" href="#">(the name of the second sub child in parent)</a><ul class='child third'>
已知是递归调用方式导致此问题,但不知如何调整。
当前代码
function loopingThroughTree($listAllEntries, $categoryTree, $i = 0): string { $childLists = [ 0 => 'second', 1 => 'third', 2 => 'fourth', 3 => 'fifth', 4 => 'sixth' ]; foreach ($categoryTree['sub'] as $categoryLevel) { $children [] = $categoryLevel; if ($categoryLevel['sub'] != null) { $listAllEntries .= '<li class="parent"><a class="' . appendArrow($categoryLevel) . '" href="' . $categoryLevel['link'] . '">' . $categoryLevel['name'] . "</a><ul class='child {$childLists[$i]}'>"; $i++; $children[] = $listAllEntries; $listAllEntries .= loopingThroughTree($listAllEntries, $categoryLevel, $i); } else { $listAllEntries .= '<li class="parent"><a href="' . $categoryLevel['link'] . '">' . $categoryLevel['name'] . '</a></li>'; } } $listAllEntries .= "</ul>"; return $listAllEntries; } $listAllEntries = '<ul class="child first">'; loopingThroughTree(listAllEntires,categoryTree)
注
我知道HTML字符串存在错误,目前仅关注逻辑实现。
分类树结构示例
parent => child[0]=>child[0]=>child[0] child[1] child[2]=>child[0] child[1]=>child[0] child[1] child[2]=>child[0] child[1] child[2] child[2] child[1] child[2] child[1]=>child[0] child[1] child[2]=>child[0] child[1] child[2]=>child[0] child[1]=>child[0] child[1] child[2]=>child[0] child[1] child[2] child[2]
问题解答
1. 递归是否为最佳方案?
递归是这类树形结构遍历的最优方案之一,因为分类树本身就是递归结构(每个节点的子节点也是同结构的树),递归写法逻辑直观、代码简洁,天然匹配树形结构的遍历需求。
如果不使用递归,替代方案是迭代法(栈/队列实现深度优先/广度优先遍历):
- 深度优先遍历:用栈存储待处理的节点及当前层级,每次弹出栈顶节点处理,再将子节点逆序压入栈(保证顺序正确);
- 广度优先遍历:用队列存储节点及层级,按层级顺序处理。
但迭代法的代码复杂度比递归高,可读性差,除非遇到递归深度超过PHP默认限制(默认100层,而这里最多6层,完全没问题),否则优先用递归。
2. 递归导致内容重复的问题修复
当前代码的核心问题是把全局的$listAllEntries作为参数传入递归函数,每次递归都在原有字符串上追加,导致子递归会重复带上父级已经生成的内容。正确的做法是让每个递归函数只负责生成当前节点及其子节点的HTML片段,而不是修改全局字符串。
修复后的代码:
function buildCategoryTreeHtml($categoryTree, $level = 1): string { // 层级对应的类名,level从1开始(对应根ul之后的子ul层级) $levelClasses = [ 1 => 'second', 2 => 'third', 3 => 'fourth', 4 => 'fifth', 5 => 'sixth' ]; $html = ''; foreach ($categoryTree['sub'] as $category) { // 生成当前li的开头 $arrowClass = !empty($category['sub']) ? 'arrow-right' : ''; $html .= '<li class="parent"><a'; if ($arrowClass) { $html .= " class=\"{$arrowClass}\""; } $html .= " href=\"{$category['link']}\">{$category['name']}</a>"; // 如果有子节点且未超过6级,递归生成子ul if (!empty($category['sub']) && $level <= 5) { $currentLevelClass = $levelClasses[$level] ?? ''; $html .= "<ul class='child {$currentLevelClass}'>"; $html .= buildCategoryTreeHtml($category, $level + 1); $html .= '</ul>'; } $html .= '</li>'; } return $html; } // 调用方式 $rootHtml = '<ul class="child first">' . buildCategoryTreeHtml($categoryTree) . '</ul>';
修复要点:
- 去掉全局的
$listAllEntries参数,每个递归调用只返回当前节点及子节点的HTML片段,父级负责拼接; - 层级参数
$level从1开始,对应子ul的类名(根ul是first,第一个子ul是second,以此类推,最多到sixth对应level5); - 移除无用的
$children数组,简化逻辑; - 增加层级判断
$level <=5,确保最多生成6级分类; - 每个递归函数只处理当前节点的子节点,不会携带父级已生成内容,彻底解决重复问题。
内容的提问来源于stack exchange,提问作者Shad0w
相关产品推荐
相关产品推荐

