🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] >[success] # 递归组件的使用 **递归组件** 有什么 **用处** 呢,一般用于 **树形结构的收缩展开菜单功能**, **层级** 是 **不固定** 的, **根据后端返回的数据进行动态渲染** 的,所以需要用 **递归组件** 来 **从 根节点 循环一直到 叶子节点** 为止。 接下来我们先 **封装一个简单的Menu组件** ,然后 **循循渐进的再封装一个递归组件**。 >[success] ## 封装Menu组件 封装一个 **简单的菜单** ,效果如下图 ![](https://img.kancloud.cn/0e/cc/0ecc76c7c45c9619866907768c19e1ea_300x147.gif) 1. **父组件** 首先在 **路由列表** 的 **路由对象** 中添加新创建的 **menu-page** 页面配置路由 **src/router/router.js** ~~~ export default [ { path: '/menu_page', name: '/menu_page', component: () => import('@/views/menu-page') } ] ~~~ 然后在 **src/views/menu-page.vue** 页面 **引入menuComponents**, **menuComponents** 中包含了 **3个** 组件,**AMenu、 AMenuItem、 ASubmenu** ,可以使用 **扩展运算符的方式引入** ,也可以使用 **解构的方式引入** 。 **src/views/menu-page.vue** ~~~ <template> <div class="menu-box"> <a-menu> <a-menu-item>111</a-menu-item> <a-menu-item>222</a-menu-item> <a-submenu> <div slot="title">333</div> <a-menu-item>333-11</a-menu-item> <a-submenu> <div slot="title">333-22</div> <a-menu-item>333-22-11</a-menu-item> <a-menu-item>333-22-22</a-menu-item> </a-submenu> </a-submenu> </a-menu> </div> </template> <script> import menuComponents from '_c/menu' // 虽然说下面的扩展运算符很方便,但不清楚哪些组件可用,用解构赋值引入就很方便 const { AMenu, AMenuItem, ASubmenu } = menuComponents export default { name: 'menu_page', components: { AMenu, AMenuItem, ASubmenu // ...menuComponents // 废弃:扩展运算符展开引入的3个组件,看起来比较简洁 } } </script> <style lang="scss"> .menu-box{ width: 300px; height: 400px; } </style> ~~~ 2. **子组件** **src/components/menu/a-menu.vue** ~~~ <template> <div class="a-menu"> <slot></slot> </div> </template> <script> export default { } </script> <style lang="scss"> // .a-menu 里的所有都设置为list-style: none .a-menu{ & *{ list-style: none; }; ul{ padding: 0; margin: 0; } } </style> ~~~ **src/components/menu/a-ment-item.vue** ~~~ <template> <li class="a-menu-item"> <slot></slot> </li> </template> <script> export default { name: 'AMentItem' } </script> <style lang="scss"> .a-menu-item{ background: rgb(90, 92, 104); color: white; } </style> ~~~ **src/components/menu/a-submenu.vue** ~~~ <template> <ul class="a-submenu"> <div class="a-submenu-title" @click="handleClick"> <slot name="title"></slot> <span class="shrink-icon" :style="{ transform: `rotate(${ showChild ? 0 : 180 }deg)` }">^</span> </div> <div v-show="showChild" class="a-submenu-child-box"> <slot></slot> </div> </ul> </template> <script> export default { name: 'ASubmenu', data(){ return{ showChild: false } }, methods: { handleClick(){ this.showChild = !this.showChild } } } </script> <style lang="scss"> .a-submenu{ background: rgb(33, 35, 39); &-title{ color: white; position: relative; .shrink-icon{ position: absolute; top: 0; right: 10px; } } &-child-box{ overflow: hidden; padding-left: 20px; } li{ background: rgb(33, 35, 39); } } </style> ~~~ 定义一个 **index.js 方便父组件引用** **src/components/menu/index.js** ~~~ import AMenu from './a-menu.vue' import AMenuItem from './a-ment-item.vue' import ASubmenu from './a-submenu.vue' export default { // 导出3个组件 AMenu, AMenuItem, ASubmenu } ~~~ >[success] ## 封装递归组件 **递归组件是根据数据层级** 来渲染的,如下图层级可以达到很深层,甚至 **无限层** 。 ![](https://img.kancloud.cn/d7/fc/d7fc2d3eee8eb6488c4ae69b8e2f16e4_300x210.gif) 下面 **组件** 中 **AMenu, AMenuItem, ASubmenu** 组件在上面的 **封装Menu组件** 的都有写过,这里就不再写了。 1. **父组件** **src/views/menu-page.vue** ~~~ <template> <div class="menu-box"> <!-- 普通菜单结构 --> <!-- <a-menu> <a-menu-item>111</a-menu-item> <a-menu-item>222</a-menu-item> <a-submenu> <div slot="title">333</div> <a-menu-item>333-11</a-menu-item> <a-submenu> <div slot="title">333-22</div> <a-menu-item>333-22-11</a-menu-item> <a-menu-item>333-22-22</a-menu-item> </a-submenu> </a-submenu> </a-menu> --> <!-- 递归组件的结构 --> <a-menu> <!-- template上是不可以写key的,所以需要写在里面 --> <template v-for="(item, index) in list"> <a-menu-item v-if="!item.children" :key="`menu_item${ index }`">{{ item.title }}</a-menu-item> <re-submenu v-else :key="`menu_item${ index }`" :parent="item" :index="index"></re-submenu> </template> </a-menu> </div> </template> <script> import menuComponents from '_c/menu' import ReSubmenu from '_c/re-submenu' const { AMenu, AMenuItem, ASubmenu } = menuComponents export default { name: 'menu_page', components: { AMenu, AMenuItem, ASubmenu, ReSubmenu }, data(){ return{ list: [ // 树形数据结构 { title: '1111' }, { title: '2222' }, { title: '3333', children: [ { title: '3333-1' }, { title: '3333-2', children: [ { title: '3333-2-1', }, { title: '3333-2-2', }, { title: '3333-2-3', children: [ { title: '3333-2-3-1', }, { title: '3333-2-3-2', } ] } ] } ] } ] } } } </script> <style lang="scss"> .menu-box{ width: 300px; height: 400px; } </style> ~~~ 2. **子组件** **src/components/re-submenu/re-submenu.vue** ~~~ <!-- 组件自己调用自己设一个条件,不然就会像递归一样一直调用组件自己,陷入死循环 --> <template> <a-submenu> <div slot="title">{{ parent.title }}</div> <template v-for="(item, i) in parent.children"> <!-- 如果无children,证明是叶子节点(最后一级),只需要显示title即可 --> <a-menu-item v-if="!item.children" :key="`menu_item${ index }_${ i }`">{{ item.title }}</a-menu-item> <!-- 如果有children继续循环 --> <re-submenu v-else :key="`menu_item${ index }_${ i }`" :parent="item"></re-submenu> </template> </a-submenu> </template> <script> import menuComponents from "_c/menu"; const { AMenuItem, ASubmenu } = menuComponents; export default { name: "ReSubmenu", components: { AMenuItem, ASubmenu, }, props: { parent: { type: Object, default: () => ({}) }, index: Number } }; </script> ~~~ 定义一个 **index.js 方便父组件引用** **src/components/re-submenu/re-submenu.vue** ~~~ import ReSubmenu from './re-submenu.vue' export default ReSubmenu ~~~ 3. **递归组件注意项** 3.1. 一定要有 **终止条件的判断** ,不然会陷入 **死循环** 。 3.2. **递归组件** 一定要给 **组件定义name值** ,它才能 **引用当前自身的组件** 。