vue2、vue3如何递归组件,自制树形tree组件

前言

当遇到树形结构数据时,返回了不确定层次的树形数据格式,需要将数据渲染出来如何自制封装一个组件来显示

例如拥有这样的tree树形数据结构

js 复制代码
const arr = [
    {
        label: '第一层数据1',
        value: 1,
        children: [
            {
                label: '第二层数据1',
                value: 2,
                children: [
                    {
                        label: '第三层数据1',
                		value: 4,
                 		children: []
                    }
                ]
            },
            {
                label: '第二层数据2',
                value: 3,
                children: []
            },
        ]
    },
    {
    	label: '第一层数据2',
        value: 5,
        children: []
    }
]

一般情况下,遇到树形结构数据使用递归,如果是组件需要递归显示,那在vue中如何实现呢

下面讲解vue2、vue3的递归用法并封装一个简易demo

vue2

在vue2中,封装一个简易的递归树形Tree组件,用于展示

1、使用name属性定义组件名,然后在组件内调用自身

在vue中的 name 属性的作用:

1、在递归组件的时候需要定义name

2、配合keep-alive include exclude 可以缓存组件

3、在Vue有报错或者调试的时候可以看到组件的name

html 复制代码
<template>
  <div class="MyTree">
      <!-- 调用自己自身形成递归 -->
      <MyTree />
    </div>
  </div>
</template>
<script>
export default {
  name: 'MyTree',
};
</script>

2、从外部传入树形数据,判定终止条件,避免无限递归造成内存溢出

html 复制代码
<template>
  <div class="MyTree">
    <div v-for="item in nodeData" :key="item.value" class="MyTree-item">
      <span>{{ item.label }}</span>
      <MyTree v-if="item.children && item.children.length > 0" :node-data="item.children" />
    </div>
  </div>
</template>
<script>
export default {
  name: 'MyTree',
  props: {
    nodeData: {
      type: Array,
      default: () => ([])
    }
  },
};
</script>

使用组件

html 复制代码
<Tree :node-data="arr" />

最核心的就完成了,接下来可以增加一些扩展功能

3、获取当前层级,点击当前节点行返回数据

对循环的item增加点击事件 toCurrentNode

js 复制代码
methods: {
    // 点击整行事件,向父组件返回当前节点和当前层级
    toCurrentNode(item) {
      const node = {
        ...item,
        level: this.getLevel()
      }
      this.$emit('currentNode', node)
      console.log(node, 'node')
    },
    // 根据父节点层数判断当前层级
    getLevel() {
      let level = 1;
      let parent = this.$parent;
      while (parent && parent.$options.name === 'MyTree') {
        level++;
        parent = parent.$parent;
      }
      return level;
    }
}

4、展开收起子节点功能

对组件增加 expandedkeys 数组参数,根据传入 唯一值,当子节点判定该数组存在 唯一值 时,展开显示子节点,并且加入vue过渡效果

完整代码如下:

html 复制代码
<template>
  <div class="MyTree">
    <div v-for="item in nodeData" :key="item.value" class="MyTree-children">
      <div class="MyTree-item" @click.stop="toCurrentNode(item)">
        <!-- 没有子节点数据不显示箭头 -->
        <template v-if="item.children && item.children.length > 0">
          <span v-if="!expandedkeysArray.includes(item.value)">→</span>
          <span v-else>↓</span>
        </template>
        <span>{{ item.label }}</span>
      </div>
      <!-- children有值 && expandedkeysArray数组存在该value值时再展开显示 -->
      <transition name="expand">
        <MyTree v-if="item.children && item.children.length > 0 && expandedkeysArray.includes(item.value)" :node-data="item.children" :style="{marginLeft: `${getLevel() * 20}px`}" />
      </transition>
    </div>
  </div>
</template>
<script>
export default {
  name: 'MyTree',
  props: {
    nodeData: {
      type: Array,
      default: () => ([])
    },
    expandedkeys: {
      type: Array,
      default: () => ([])
    },
  },
  computed: {
    // 实时更新父组件的值
    expandedkeysArray: {
      get() {
        return this.expandedkeys
      },
      set(newVal) {
        this.$emit('update:expandedkeys', newVal)
      }
    }
  },
  methods: {
    // 点击整行事件,向父组件返回当前节点和当前层级
    toCurrentNode(item) {
      const node = {
        ...item,
        level: this.getLevel()
      }
      this.$emit('currentNode', node)
      // 没有展开的子节点才push展开
      if (!this.expandedkeysArray.includes(item.value)) {
        this.expandedkeysArray.push(item.value)
        return
      }
      // 已经展开的子节点删除收起
      const index = this.expandedkeysArray.indexOf(item.value)
      if (index !== -1) {
        this.expandedkeysArray.splice(index, 1)
      }
    },
    // 根据父节点层数判断当前层级
    getLevel() {
      let level = 1;
      let parent = this.$parent;
      while (parent && parent.$options.name === 'MyTree') {
        level++;
        parent = parent.$parent;
      }
      return level;
    }
  }
};
</script>
<style lang="less" scoped>
.MyTree{
  .MyTree-item{
    cursor: pointer;
    border-bottom: 1px solid #E4E7ED;
    display: flex;
    align-items: center;
    padding: 5px 10px;
    &:hover{
      background: #F2F6FC;
    }
  }
}
.expand-enter-active, .expand-leave-active {
  transition: all 0.3s;
}

.expand-enter, .expand-leave-to {
  transform: scaleY(0);
  transform-origin: top;
  opacity: 0;
}
</style>

使用组件

html 复制代码
<Tree :node-data="arr" :expandedkeys.sync="expandedKeys"  @currentNode="currentNode" />
js 复制代码
data() {
    expandedKeys: [1, 5]
}

效果如下

还可以继续扩展,例如增加复选框选择自定义传入参数键名等,这里暂不做过多操作,核心展示如何递归组件,包括递归第三方UI库的组件也是可以的

vue3

在vue3中,由于不使用选项式API ,改用了组合式API 时,若想要递归组件,组件的name属性该如何定义呢

有如下几种方式

1、自动根据文件名生成对应的 name 选项

在vue3中,使用了 <script setup> 语法糖模式会自动根据文件名生成对应的 name 选项

上述的组件名分别为 IndexHelloWorldVModel

此方式不适用 文件夹/index.vue结构,例如 MyTree/index.vue,组件名称为Index

2、再多使用一个 <script> 标签来定义name

html 复制代码
<script lang="ts" setup>

</script>

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

3、使用插件 unplugin-vue-define-options

html 复制代码
<script setup lang="ts">
defineOptions({
  name: 'MyTree',
})
</script>

不过这个插件的原理实际也是和方案2一样再加上一个

4、组件内引入自身文件进行递归(只适用于递归组件)

由于vue3的组件变为通过 变量引用 而不是 基于字符串组件名 注册的,所以可以引用自身文件然后直接使用也可以形成递归

代码如下

html 复制代码
<template>
  <div>
    myTree
    <MyTree />
  </div>
</template>
<script lang="ts" setup>
import MyTree from './index.vue'
</script>
相关推荐
华玥作者1 小时前
[特殊字符] VitePress 对接 Algolia AI 问答(DocSearch + AI Search)完整实战(下)
前端·人工智能·ai
Mr Xu_1 小时前
告别冗长 switch-case:Vue 项目中基于映射表的优雅路由数据匹配方案
前端·javascript·vue.js
前端摸鱼匠2 小时前
Vue 3 的toRefs保持响应性:讲解toRefs在解构响应式对象时的作用
前端·javascript·vue.js·前端框架·ecmascript
sleeppingfrog2 小时前
zebra通过zpl语言实现中文打印(二)
javascript
lang201509282 小时前
JSR-340 :高性能Web开发新标准
java·前端·servlet
好家伙VCC3 小时前
### WebRTC技术:实时通信的革新与实现####webRTC(Web Real-TimeComm
java·前端·python·webrtc
未来之窗软件服务3 小时前
未来之窗昭和仙君(六十五)Vue与跨地区多部门开发—东方仙盟练气
前端·javascript·vue.js·仙盟创梦ide·东方仙盟·昭和仙君
baidu_247438613 小时前
Android ViewModel定时任务
android·开发语言·javascript
嘿起屁儿整3 小时前
面试点(网络层面)
前端·网络
VT.馒头3 小时前
【力扣】2721. 并行执行异步函数
前端·javascript·算法·leetcode·typescript