vue递归组件

vue递归组件封装

上效果(递归生成树形菜单,无限层数都可以!)
在这里插入图片描述

  • 首先需要使用递归方法把数据变成树形结构
//后端传过来的数据长这样子 而且是变化的不确定有多少层的数据
list = [
	{ id:1, name: '张三', parent_id: 0, children: [] },
	{ id:2, name: '李四', parent_id: 0, children: [] },
	{ id:3, name: '王五', parent_id: 1, children: [] },
	{ id:4, name: '六六', parent_id: 3, children: [] },
	{ id:4, name: '七七', parent_id: 2, children: [] },
]
// 这个时候就需要用到递归来处理这种数据
// 递归生成树形结构
export function initTree (arr, parent_id, i = 0) {
	// arr 变量数据
	// 第一次以后:根据id去查询parent_id相同的(相同为子数据)
	// 第一次:查找所有parent_id为0的数据组成第一级
	const child = arr.filter(item => item.parent_id == parent_id)
	// 第一次:循环parent_id为0的数组
	if (child.length !== 0) {
			let level = i + 1
			return child.map(item => ({
				...item,
				level,
				// 当前存在id(id与parent_id应该是必须有的)调用initTree() 查找所有parent_id为本id的数据
				// childs字段写入
				children: initTree(arr, item.id, level)
		}))
	}
}
  • 最后生成的树形结构为这样子
// 第一个参数是数组 第二个是最顶级的parent_id
const treeList = initTree (list, 0)
treeList = [
	{ id:1, name: '张三', parent_id: 0,
		children: [
			{ id:3, name: '王五', parent_id: 1, 
				children: [
					{ id:4, name: '六六', parent_id: 3, children: [] },
				] 
			},
		] 
	},
	{ id:2, name: '李四', parent_id: 0,
		children: [
			{ id:4, name: '七七', parent_id: 2, children: [] },
		]
	},
]
  • 生成递归组件(自己调自己)
<template>
  <ul class="menu">
    <li v-for="item in menuList" :key="item.name" :class="['menu-item', item.selectName]">
        <div :class="['menu-item-content', item.children && item.children.length ? 'parent': ['animated', 'bounceInLeft']]" :style="{'padding-left': item.level * 25 + 'px'}" @click="toggleShow(item)">
            <img src="../../assets/image/close.png" alt="" v-show="item.children && item.children.length && !item.showChild">
            <img src="../../assets/image/open.png" alt="" v-show="item.children && item.children.length && item.showChild">
            <van-icon name="arrow" class="leftIcon" v-if="item.children && item.children.length" />
            <span class="menu-item-name" :id="`span${item.id}`" :ref="`ref${item.id}`">
              {{item.name}}
              <van-icon name="passed" class="nowFill" v-show="!item.children && item.nowFill" />
              <van-icon name="passed" class="nowFill" style="right: -30px;" v-show="item.children && item.nowFill" />
              <van-icon name="warn-o" class="nowFill minusIcon"  v-show="item.children && !item.nowFill" />
              <!-- <van-icon name="contact" style="right: -30px;" class="nowFill" v-show="item.is_staff && !icon" />
              <van-icon name="cluster-o" style="right: -30px;" class="nowFill" v-show="!item.is_staff && !icon && type===1" /> -->
            </span>
        </div>
        <Menu v-if="item.showChild && item.children && item.children.length" :list="item.children" @clickOne="clickOne"></Menu>
    </li>
</ul>
</template>

<script>
import _ from 'lodash'
import '../../utils/animate.min.css'
export default {
  name: 'Menu',
  props: ['list'],
  watch: {
    list: {
      immediate: true,
      deep: true,
      handler (newV) {
        this.menuList = _.cloneDeep(newV)
        this.menuList = this.menuList.map(item => {
          item.showChild = true
          return item
        })
      }
    }
  },
  data () {
    return {
      menuList: [],
      beforeItem: {},
      beforeDom: null
    }
  },
  methods: {
    toggleShow(item) {
      if (item.children && item.children.length) {
        item.showChild = !item.showChild
        this.$forceUpdate()
      } else {
        this.$emit('clickOne', item)
        if (sessionStorage.getItem('dom')) {
          console.log(sessionStorage.getItem('dom'))
          this.beforeDom = document.getElementById(sessionStorage.getItem('dom'))
          this.beforeDom.style.color = 'white'
        }
        let currentDom = document.getElementById(`span${item.id}`)
        currentDom.style.color = '#05f5a5'
        sessionStorage.setItem('dom', `span${item.id}`)
      }
    },
    clickOne (item) {
      this.$emit('clickOne', item)
    }
  },
  destroyed () {
    sessionStorage.removeItem('dom')
  }
}
</script>

<style lang="scss">
.menu {
  color: white;
  width: 100%;
  height: 100%;
  font-size: 14px;
  margin-top: 10px;
  .menu-item {
    width: 100%;
    .menu-item-content {
      height: 35px;
      line-height: 35px;
      display: flex;
      align-items: center;
      position: relative;
      img {
        width: 30px;
        height: 30px;
      }
    }
    .parent {
      background: rgba(255, 255, 255, 0.247);
      margin-bottom: 5px;
    }
    .menu-item-name {
      margin-left: 5px;
      position: relative;
    }
  }
}
.selected {
  color: #05f5a5 !important;
}
.leftIcon {
  position: absolute !important;
  top: 50%;
  transform: translate(0, -50%);
  right: 20px;
  color: white !important;
}
.nowFill {
  position: absolute !important;
  top: 50%;
  transform: translate(0, -50%);
  color: #05f5a5 !important;
  right: -40px;
  font-size: 18px !important;
}
.minusIcon {
  color: red !important;
  right: -30px;
}
</style>

可直接使用 传一个list就行 icon用的vant的。

### Vue 递归组件的使用方法 在 Vue 中,递归组件是一种能够调用自身的特殊组件。这种设计模式非常适合处理具有层次结构的数据,例如树形结构、嵌套菜单或文件夹列表。 #### 创建递归组件的关键点 1. **命名组件** 在 Vue 的单文件组件中,必须显式地为递归组件指定 `name` 属性。只有这样,组件才能识别自身并实现递归逻辑[^2]。 2. **终止条件** 为了避免无限递归,必须设置清晰的退出条件。通常可以通过判断数据是否存在或者满足特定条件来控制递归停止[^3]。 3. **传递数据** 数据通常是通过 `props` 进行父子组件之间的通信。父组件将数据传递给子组件,而子组件则继续将其部分数据向下传递[^1]。 --- #### 示例:树形结构的递归组件 假设我们要构建一个树形结构的目录展示功能,以下是完整的代码示例: ##### 父组件 (App.vue) ```vue <template> <div id="app"> <!-- 将根节点数据传递给递归组件 --> <TreeItem :tree-data="rootData" /> </div> </template> <script> import TreeItem from './components/TreeItem.vue'; export default { name: 'App', components: { TreeItem, }, data() { return { rootData: [ { title: 'Folder 1', children: [ { title: 'File 1-1' }, { title: 'File 1-2' }, ], }, { title: 'Folder 2', children: [ { title: 'Subfolder 2-1', children: [{ title: 'File 2-1-1' }], }, ], }, ], }; }, }; </script> ``` ##### 子组件 (TreeItem.vue) ```vue <template> <ul> <li v-for="(node, index) in treeData" :key="index"> {{ node.title }} <!-- 判断是否有子节点,如果有,则递归渲染 --> <TreeItem v-if="node.children && node.children.length > 0" :tree-data="node.children" /> </li> </ul> </template> <script> // 定义组件名称以便支持递归 export default { name: 'TreeItem', props: { treeData: { type: Array, required: true, }, }, }; </script> ``` --- #### 关键解析 1. **递归的核心机制** - 在 `TreeItem.vue` 中,通过 `<TreeItem>` 调用了自身。 - 使用 `v-if` 控制递归是否继续执行,从而避免无限循环[^3]。 2. **数据流的设计** - 父组件 (`App.vue`) 提供顶层数据并通过 `props` 传递给子组件。 - 子组件接收数据后,进一步拆解并将剩余的部分再次传递给下一层递归实例。 3. **动态生成 DOM 结构** - 使用 `v-for` 遍历数组中的每一项,并根据其内容决定是否需要继续递归渲染子节点[^4]。 --- #### 实际应用场景 递归组件广泛应用于以下场景: - 文件管理器中的文件夹和子文件夹展示[^1]。 - 多级导航菜单的实现[^4]。 - 注释系统的回复链展示[^2]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值