likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 34

前言

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

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

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

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

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

<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>

使用组件

<Tree :node-data="arr" />

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

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

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

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

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;
    }
}

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

4、展开收起子节点功能

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

完整代码如下:

<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>

使用组件

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

效果如下

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

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

vue3

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

有如下几种方式

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

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

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

上述的组件名分别为 IndexHelloWorldVModel

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

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

<script lang="ts" setup>

</script>

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

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

<script setup lang="ts">
defineOptions({
  name: 'MyTree',
})
</script>

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

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

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

代码如下

<template>
  <div>
    myTree
    <MyTree />
  </div>
</template>
<script lang="ts" setup>
import MyTree from './index.vue'
</script>
转载自:https://juejin.cn/post/7271592008342831167
评论
请登录