使用Vue渲染函数动态将标题按层级包裹并分组为列表项
使用Vue渲染函数动态将标题按层级包裹并分组为列表项
嘿,这个需求我懂!你想要做一个<Steps>组件,能自动把同级标题(比如h3和h3)及其后续的所有内容(包括层级更低的标题,比如h4)打包成<li>,外层套<ol>对吧?刚好可以用Vue的渲染函数h()来搞定,我给你一步步拆解实现思路和代码。
核心思路拆解
要实现这个逻辑,关键要解决两个问题:
- 确定基准标题层级:找到第一个出现的h2/h3/h4,把它的层级作为“触发新列表项”的标准(比如第一个是h3,那只有后续的h3才会开启新的
<li>) - 节点分组:遍历所有插槽节点,把从当前基准标题到下一个基准标题之间的所有内容归为一组,每组对应一个
<li>
具体代码实现(Vue 2 选项式写法)
<script> export default { render(h) { // 1. 获取组件默认插槽的所有节点 const slots = this.$slots.default || []; if (slots.length === 0) return h('ol'); // 2. 找到第一个标题节点,确定基准层级 let baseLevel = null; for (const node of slots) { if (node.tag && ['h2', 'h3', 'h4'].includes(node.tag)) { baseLevel = parseInt(node.tag.slice(1)); // 从h3提取数字3 break; } } // 如果插槽里没有标题,直接返回包裹所有内容的ol if (!baseLevel) return h('ol', slots); // 3. 遍历节点,按基准标题分组 const groups = []; let currentGroup = []; for (const node of slots) { // 遇到基准层级的标题时,先把当前分组存入结果,再新建分组 if (node.tag && node.tag === `h${baseLevel}`) { if (currentGroup.length > 0) { groups.push(currentGroup); currentGroup = []; } } // 将当前节点加入当前分组 currentGroup.push(node); } // 别忘了把最后一个分组加入结果 if (currentGroup.length > 0) { groups.push(currentGroup); } // 4. 渲染成ol包裹的li列表 const listItems = groups.map(group => h('li', group)); return h('ol', listItems); } }; </script>
Vue 3 组合式API写法
如果你用的是Vue 3,也可以用组合式API来实现:
<script setup> import { h, useSlots, defineRender } from 'vue'; const render = () => { // 获取插槽节点 const slots = useSlots().default?.() || []; if (slots.length === 0) return h('ol'); // 确定基准标题层级 let baseLevel = null; for (const node of slots) { if (node.type && ['h2', 'h3', 'h4'].includes(node.type)) { baseLevel = parseInt(node.type.slice(1)); break; } } if (!baseLevel) return h('ol', slots); // 节点分组逻辑和Vue2一致 const groups = []; let currentGroup = []; for (const node of slots) { if (node.type && node.type === `h${baseLevel}`) { if (currentGroup.length > 0) { groups.push(currentGroup); currentGroup = []; } } currentGroup.push(node); } if (currentGroup.length > 0) groups.push(currentGroup); // 渲染最终结构 const listItems = groups.map(group => h('li', group)); return h('ol', listItems); }; // 注册渲染函数 defineRender(render); </script>
验证你的测试案例
拿你给出的第一个输入来测试:
<Steps> <h3>heading a</h3> <p>paragraph a</p> <div class="code-block"><pre>const x = 1</pre></div> <h4>this should not create an li</h4> <h3>heading b</h3> <p>paragraph b</p> </Steps>
组件会输出完全符合你要求的结构:
<ol> <li> <h3>heading a</h3> <p>paragraph a</p> <div class="code-block"><pre>const x = 1</pre></div> <h4>this should not create an li</h4> </li> <li> <h3>heading b</h3> <p>paragraph b</p> </li> </ol>
第二个只有单个基准标题的输入,也会把所有内容都包裹在同一个<li>里,完全符合预期。
边界情况说明
- 如果插槽里没有任何标题节点,组件会直接把所有内容包裹在
<ol>里返回 - 如果插槽开头是非标题内容,这些内容会被放到第一个
<li>里,直到遇到第一个基准标题
备注:内容来源于stack exchange,提问作者dbzx10299
相关产品推荐
相关产品推荐

