Vue递归组件实现树形结构菜单

9,564 阅读3分钟

    Tree 组件是递归类组件的典型代表,它常用于文件夹、组织架构、生物分类、国家地区等等,世间万物的大多数结构都是树形结构。使用树控件可以完整展现其中的层级关系,并具有展开收起选择等交互功能。
    如图所示,我们要实现的就是这样一个效果。之前我们写树状结构都是用jQuery来实现的,用Vue怎么实现呢?

一、数据部分模拟

  menuList:[
            {
                title:'菜单1',
                children:[
                    {
                        title:'菜单1-1',
                        children:[
                                {title:'菜单1-1-1'},
                                {title:'菜单1-1-2'},
                                {title:'菜单1-1-3'}
                            ]
                        },
                    {title:'菜单1-2'},
                    {title:'菜单1-3'}
                ]
            },
            {title:'菜单2'},
            {title:'菜单3'}
    ]
  

二、组件各部分实现

Menu.vue

首先我们来写个menu组件,这里放个ul列表,里面的内容,用插槽来表示。
<template>
    <ul>
       <slot></slot>
    </ul>
</template>

<script>
    export default {
        name: "Menu"
    }
</script>

MenuItem.vue

如果没有子节点,所要展示的标题
<template>
    <li><slot></slot></li>
</template>

<script>
    export default {
        name: "MenuItem"
    }
</script>

SubMenu.vue

<template>
      <div>
          <div class="title" @click="change">
              <slot name="title"></slot>
              <!--name='title'区分插槽 -->
          </div>
          <div class="sub" v-show="flag">
              <slot></slot>
          </div>
      </div>
</template>

<script>
    export default {
        name: "SubMenu",
        data(){
            return {flag:false}
        },
        methods:{
            change(){
                this.flag=!this.flag
            }
        }
    }
</script>

<style>
.sub{
    padding-left:20px;
}
</style>

三、递归组件ReSubMenu.vue

这里到了我们实现树形结构思想的重点,即递归组件。当我们重复判断有没有子节点,并做出相应的展示的时候,这里就可以使用递归组件了。方便快捷,你值得拥有。

由于有子节点会循环SubMenu这部分操作,所以单独提出来放到ReSubMenu组件中
<template>
    <SubMenu>
        <template #title><!--#title为了标识区分插槽-->
            {{data.title}}
        </template>
        <template v-for="child in data.children">
            <MenuItem :key="child.title" v-if="!child.children">{{child.title}}</MenuItem>
            <!--ReSubMenu跟name的名字保持一致,相当于循环使用该组件-->
            <ReSubMenu v-else :key="child.title" :data="child"></ReSubMenu>
        </template>
    </SubMenu>
</template>

<script>
    import SubMenu from './SubMenu'
    import MenuItem from './MenuItem'
    export default {
        name: "ReSubMenu",//可以使用递归组件
        props:{
            data:{
                type:Object,//属性校验,为对象数据类型,并且如果没有赋值,默认给一个空对象
                default:()=>({})
            }
        },
        components:{
            SubMenu,
            MenuItem
        }
    }
</script>

四、整合实现

<div id="app">
   <Menu>
        <template v-for="menu in menuList">
                <MenuItem :key="menu.title" v-if="!menu.children">{{menu.title}}</MenuItem>
                <!--这部分是如果有孩子节点则会循环这部分操作,所以要单独提取出来-->
               <!-- <SubMenu v-else>
                    <template #title>{{menu.title}}</template>
                    <template v-for="child in menu.children">
                        <MenuItem>{{child.title}}</MenuItem>
                    </template>
                </SubMenu>-->
                <ReSubMenu :key="menu.title" v-else :data="menu"></ReSubMenu>
        </template>
   </Menu>
<div>
<script>
    import Menu from './Menu'
    import MenuItem from './MenuItem'
   // import SubMenu from './SubMenu'
    import ReSubMenu from './ReSubMenu'
    export default {
        data(){
            return{
            //这里的数据我就模拟一个了
                menuList:[
                    {
                        title:'菜单1',
                        children:[
                            {
                                title:'菜单1-1',
                                children:[
                                    {title:'菜单1-1-1'},
                                    {title:'菜单1-1-2'},
                                    {title:'菜单1-1-3'}
                                ]
                            },
                            {title:'菜单1-2'},
                            {title:'菜单1-3'}
                        ]
                    },
                    {title:'菜单2'},
                    {title:'菜单3'}
                ]
            }
        },
        components:{
            Menu,MenuItem,ReSubMenu
        }
    }
</script>

注:本节部分语句参考 https://juejin.cn/book/6844733759942557704/section/6844733760152272910