vue递归组件—开发树形组件Tree--(构建树形菜单)

news2025/1/21 21:53:52

在 Vue 中,组件可以递归的调用本身,但是有一些条件:

  • 该组件一定要有 name 属性
  • 要确保递归的调用有终止条件,防止内存溢出

不知道大家有没遇到过这样的场景:渲染列表数据的时候,列表的子项还是列表。如果层级少尚且可以用几个for循环搞定,但是层级多或者层级不确定就有点无从下手了。

其实这就是树形结构数据,像常见的例如导航、空间或逻辑组织、页面定位、级联选择等,其结构可展开或折叠,都属于这种结构。

效果展示

以上就是使用组件递归,并加入简单交互的展示效果。点击节点会在控制台输出节点对应的数据,如果有子节点,则会展开或收起子节点。接下来我们就看看如何实现以上效果吧! 

渲染完整数据

渲染数据这一步非常简单,首先是把树形结构封装成一个列表组件,其次判断每一项有没有子节点,如果有子节点,再使用自身组件去渲染就可以了。

递归组件:src/components/tree-folder.vue


//项目用到vant-ui库

<template>
  <div class="tree-item">
    <div v-for="item in treeData" :key="item.id">
      <div class="item-title">
		<span v-text="item.name"></span>
		<span v-if="item.children && item.children.length">
          //vant组件库图标 看个人需求换成自己需要的
		  <van-iconname="arrow-down"/>
        </span>
      </div>
      <div v-if="item.children && item.children.length" class="item-childen">
        <tree-folder :treeData="item.children"></tree-folder>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TreeFolder',
  props: {
    treeData: {
      type: Array,
      default: () => []
    }
  }
}
</script>

<style lang="stylus">
.tree-item
	.item-title
		padding: 4px 8px

		.name
			color: #000

		.negative-rotate
			transform: rotate(-180deg)

	.item-childen
		padding-left: 20px
</style>

父组件: src/home.vue

<template>
  //引用递归组件,并传递数据
  <TreeFolder :treeData="treeData" />
</template>

<script>
const treeData = [
  { id: 1, name: '一级1' },
  {
    id: 2,
    name: '一级2',
    children: [
      { id: 3, name: '二级2-1' },
      { id: 4, name: '二级2-2' }
    ]
  },
  {
    id: 5,
    name: '一级3',
    children: [
      {
        id: 6,
        name: '二级3-1',
        children: [
          { id: 7, name: '三级3-1-1' },
          { id: 8, name: '三级3-1-2' }
        ]
      },
      { id: 9, name: '二级3-2' },
      { id: 10, name: '二级3-3' }
    ]
  }
]
import TreeFolder from '@/components/tree-folder.vue'
export default {
  components: {
    TreeFolder
  },
  data() {
    return {
      treeData: treeData
    }
  }
}
</script>

效果如下

获取节点数据

接下来我们要做的是,点击节点时在控制台输出对应的数据。首先我们使用 $emit,将一级节点的 item 传递出去,也就是子传父的方法,相信大家都会。

其次是将内层节点的数据传递出去,同样使用子传父的方法,只是我们需要给组件里面的 tree-folder绑定@tree-node-click="$emit('tree-node-click', $event)",这样每次子级每次都可以调用父级的 tree-node-click 方法,父级又调用它的父级 tree-node-click 方法,最终调的都是最外层的 tree-node-click 方法,我们只需要在这个过程中,把数据传递过去就可以了。这块有点绕,相信大家多看几遍应该可以看懂。修改如下:

递归组件:src/components/tree-folder.vue

//方法名可以自取 不一定非是'tree-node-click'

<div class="item-title">
  <span v-text="item.name"></span>
  <span v-if="item.children && item.children.length">
    //vant组件库图标 看个人需求换成自己需要的
	<van-icon name="arrow-down"/>
  </span>
</div>
<div v-if="item.children && item.children.length" class="item-childen">
  <tree-folder
    :treeData="item.children"
    @tree-node-click="$emit('tree-node-click', $event)"
  ></tree-folder>
</div>
...
methods: {
 itemNodeClick(item) {
  this.$emit('tree-node-click', item)
 }
}

父组件: src/home.vue

<TreeFolder:tree-data="treeData" @tree-node-click="nodeClick" />
...
methods: {
 nodeClick(val) {
   console.log(val)
 }
}

效果如下

动态展开收起并给点击项添加激活样式和设置图标的样式

动态展开收起的思路是给组件设置一个数组,数组中存放的是当前列表中需要展开的节点的id,当点击节点的时候添加或删除节点id,然后判断每个节点的id在不在这个数组,在则显示子节点,不在则隐藏子节点。 

点击项添加激活样式的思路是给组件绑定一个变量,变量存放的值是点击当前列表中节点的id,当点击节点的时候通过id添加相对应的样式。

图标样式的思路是根据数组中存放的的节点的id,判断节点的id如果在这个数组,则旋转 -180deg,不在则回到最初状态。 

 递归组件:src/components/tree-folder.vue


//项目用到vant-ui库

<template>
  <div class="tree-item">
    <div v-for="item in treeData" :key="item.id">
      <div class="item-title">
		<span
         :class="{ 'fc-theme': item.id == curNameId }"  // fc-theme激活样式的类名
		  v-text="item.name">
        </span>
		<span v-if="item.children && item.children.length">
          //vant组件库图标 看个人需求换成自己需要的
		  <van-icon
			name="arrow-down"
			style="transition: transform 0.3s" // 加一个延迟动画效果
			:class="{ 'negative-rotate': isOpen(item.id), 'fc-theme': item.id == 
             curNameId }"/>  // 根据id判断是否旋转图标和添加激活样式
		 </span>
      </div>
      <div v-if="item.children && item.children.length" v-show="isOpen(item.id)"                 
        class="item-childen">
        //这里是重点:current="curNameId",否则无法实现动态添加激活样式
        <tree-folder :treeData="item.children" :current="curNameId" @tree-node-            
         click="$emit('tree-node-click', $event)"> 
        </tree-folder>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'TreeFolder',
  props: {
    treeData: {
      type: Array,
      default: () => []
    },
    current: Number //保存当前点击节点的id
  },
  data() {
   return {
    expandedKeys: [], // 当前列表需要展开的节点id组成的数组
    curNameId: 0 //保存current的值
  }
 },
	computed: {
		isOpen() {
			return function (id) {
				// 判断节点id在不在数组中,在则显示,不在则隐藏
				return this.expandedKeys.includes(id)
			}
		}
	},
	watch: {
        //监听当前点击节点的id
		current: {
			handler(num) {
				this.curNameId = num
			},
			deep: true,
			immediate: true
		}
	},
	methods: {
		itemNodeClick(item) {
			this.$emit('tree-node-click', item)
			if (item.children && item.children.length) {
				let index = this.expandedKeys.indexOf(item.id)
				if (index > -1) {
					// 如果当前节点id存在数组中,则删除
					this.expandedKeys.splice(index, 1)
				} else {
					// 如果当前节点id不存在数组中,则添加
					this.expandedKeys.push(item.id)
				}
			}
		}
	}
}
</script>

<style lang="stylus">
.tree-item
	.item-title
		padding: 4px 8px

		.name
			color: #000

		.negative-rotate
			transform: rotate(-180deg)

	.item-childen
		padding-left: 20px
</style>

父组件: src/home.vue

<TreeFolder:tree-data="treeData" :current="curNameId" @tree-node-click="nodeClick" />
...
data() {
 return {
   curNameId: 0
 }
},

methods: {
 nodeClick(val) {
  this.curNameId = val.id
  this.$set(val, 'curNameId', this.curNameId)
 }
}

最终效果

 

附上一个疑问的解答

组件调用组件自己的时候,$emit了tree-node-click事件,这个功能主要是做什么的,还有为什么$emit('tree-node-click', $event)第二个参数是$event而不是一个可变数据

 这是为了实现内层的子传父功能,使用了@tree-node-click="$emit('tree-node-click', $event)",在内层执行this.$emit("tree-node-click", item) 时候,其实就是执行父组件的 $emit('tree-node-click', $event),然后又会执行爷爷组件的 $emit('tree-node-click', $event),最终执行的是父组件: src/home.vue 的 nodeClick 事件,其实参数一直是item。

 以上就是今天的分享!是有点绕,请耐心看完思考并且自己动手试一试。有兴趣的小伙伴可以动手试一哈,把组件进一步封装,或修改成自己想要的样式和效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408491.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue基础--webpack介绍以及基础配置

写在最前&#xff1a;实际开发中需要自己配置webpack吗&#xff1f; 答案&#xff1a;不需要&#xff01; 实际开发中会使用命令行工具&#xff08;俗称CLI&#xff09;一键生成带有webpack的项目开箱即用&#xff0c;所有webpack的配置项都是现成的&#xff01;我们只需要知道…

python web开发基础

网站是存储在服务器上的文件&#xff0c;服务器是托管网站的计算机。这些服务器连接到一个称为 Internet 的网络。访问这些网站的计算机称为“客户端”。 要访问网站需要知道其IP地址&#xff0c;IP 地址是一串唯一的数字。每个设备都有一个 IP 地址。可以在控制台cmd输入命令…

前端笔记(10) Vue3 Router 监听路由参数变化

前言 Vue Router是开发Vue项目的必不可少的工具&#xff0c;也是极为重要的学习要点。 本篇介绍下Vue Router的基础使用和如何监听路由参数变化。 Vue Router入门 1 安装Router 安装Vue Router非常方便&#xff0c;只需执行一个命令&#xff0c;如果还不知道怎么搭建Vue项目…

详解 HttpServletResponse

详解 HttpServletResponse 核心方法代码示例1.设置响应状态码2.设置响应头3.设置响应内容&#xff08;1&#xff09;响应一个网页&#xff08;简单HTML&#xff09;&#xff08;2&#xff09;响应一个网页&#xff08;复杂HTML&#xff09;返回已有的一个网页1.重定向2.转发返回…

uniapp - 编译微信小程序项目的微信授权登录、获取微信手机号登录、最新版微信直接登录、手机与验证码登录的示例源码(适用于 uniapp 微信小程序项目,源代码直接开箱即用)超级详细的代码及注释

效果图 uniapp 项目编译微信小程序,一些常见的登录方式及源代码,示例代码干净整洁无BUG拿来即用。 本文示例实现了 uniapp 微信小程序项目的登录功能,包含微信授权登录、获取微信手机号登录、最新直接登录等, 你可以选择一个,直接复制源代码,稍微改改就能应用到你的项目…

后台管理系统

后台管理系统主要是我们内部人员使用的一款用来管理我们产品的一个系统&#xff0c;然后呢&#xff0c;我们今天写的呢是一个电商的后台管理系统。主要是可以用来管理我们的用户还有我们是商品的。 我们这个系统呢采用的是一个前后端分离的模式&#xff0c;主要是使用后端给我…

Vue 当页面进入全屏状态时element-ui的el-select下拉菜单不显示问题

在前两天进行页面全屏时&#xff0c;一切都还好好的&#xff0c;可当使用element-ui中的el-select时&#xff0c;下拉菜单却怎么也显示不出来&#xff0c;但只要退出全屏状态&#xff0c;立马就好。 非全屏时&#xff1a; 全屏时&#xff1a; 开始我以为是层级问题&#xff0…

前端加载高德离线地图的解决方案

核心是需要下载地图瓦片放在本地&#xff0c;脱离在线地图服务&#xff0c;实现离线加载地图。使用BIGMap工具下载地图离线瓦片到本地 下载地址&#xff1a;http://www.bigemap.com/reader/download/detail201802015.html BIGEMAP GIS Office-全能版需要注册试用版&#xff08;…

web前端面试宝典——带你直击面试重难点(40个经典题目,涵盖近90%的考点,码字2w,干货满满!)

系列文章目录 JavaScript 知识梳理&#xff0c;收录了web前端面试 95%以上 的高频考点&#xff0c;满满的干货。给你做一个高效的知识梳理&#xff0c;为你的面试保驾护航&#xff01; 内容参考链接HTML & CSS 篇HTML & CSS 篇JavaScript 篇&#xff08;一&#xff09;…

【微信小程序】条件渲染和列表渲染

&#x1f352;&#x1f352; 观众老爷们好啊&#xff0c;牛牛又更新了&#xff0c;上文我们详细了解了微信小程序中的事件绑定&#xff0c;那么今天我们就来讲讲WXML语法中的列表渲染和条件渲染&#xff0c;它俩也是非常重要的知识点&#xff0c;赶紧学起来吧。 &#x1f352;&…

如何更改ElementUI组件的图标大小以及标签属性

话不多说&#xff0c;直接上菜。 ElementUI提供的Rate评分组件的默认大小是这样的 图标太小了&#xff0c;想设置宽高、行高、尺寸&#xff0c;但代码不起作用。 打开浏览器调试&#xff0c;发现是用font-size设置才有用。 由此代码存在优先级问题&#xff0c;要提高优先级。…

vscode里面使用vue的一些插件,方便开发

1、vue 2 Snippets &#xff08;vue语法提示&#xff09; vue提示这个也可以 1.1 Vue VSCode Snippets 2、vetur Vetur支持.vue文件的语法高亮显示&#xff0c;除了支持template模板以外 3、Element UI Snippets(饿了么的提示) 4、indent-rainbow&#xff08;缩进高亮提示) 5…

Vue Element table表格实现表头自定义多类型动态筛选 , 目前10种筛选类型,复制即用

一、效果图 目前10种筛选类型 看看是否是你需要的&#xff0c;本文可能有点长 &#xff0c;我尽可能的给讲清楚&#xff0c;包括源码附上 二、无聊发言 点击当前行跳转部分数据后缀追加图标某列数据根据状态增加颜色标识 三、前言 实现图中的表格&#xff0c;特定的两个要求&…

css-两种画弧线方法

第一种&#xff1a;::after <template><view><view class"bg"></view></view> </template> <style> .bg{background-color: pink; } .bg::after{content: ;position: absolute;width: 160%;height: 100px;background: sk…

多项目版本管理:monorepo 策略

monorepo 是什么 一个产品会有多个项目&#xff0c;每个项目之间会存在版本同步的问题&#xff0c;如何在其中一个项目发布上线后&#xff0c;保证每个项目版本升级后的版本同步问题&#xff0c;提出的解决方案就是 monorepo 策略。 monorepo 是一种将多个项目代码存储在一个…

【小程序项目开发-- 京东商城】uni-app之商品列表页面 (上)

&#x1f935;‍♂️ 个人主页: 计算机魔术师 &#x1f468;‍&#x1f4bb; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 该文章收录专栏 ✨ 2022微信小程序京东商城实战 ✨ 文章目录一、前言介绍二、创建goodlist 分支&#xff08;选读*)三、商品列…

H5页面跳转小程序的三种方式

文章目录前言一、web-view标签返回小程序1.小程序启动页面只写web-view标签跳转到授权页面。2.编写auth.html3、把auth.html放到服务器就可以测试访问&#xff0c;打开小程序默认进入启动页面中的webview跳转到H5&#xff0c;授权成功后&#xff0c;通过wx.miniProgram.reLaunc…

Vue自定义网页顶部导航栏

Vue自定义web网页顶部导航栏 说明&#xff1a;此组件是为论坛类项目定制的一个实用的顶部导航栏&#xff0c;当然也可以用于其他的Web项目&#xff0c;只需要稍作修改便可以达到想要的效果。其中导航栏包含了搜索栏&#xff0c;用户头像&#xff0c;以及基本的导航标题。导航栏…

uniapp小程序自定义顶部导航栏,输入框软键盘把顶部顶上去的解决方法

首先在小程序input标签增加:adjust-position"false"的属性&#xff0c;然后已经可以把软键盘不使上方顶出&#xff0c;但是输入框也会因此被遮挡 解决方法&#xff1a;在input输入框聚焦的方法中增加操作 focus"inputBindFocus" 定义方法 inputBindFoc…

【vue3】基础概念的介绍

⭐【前言】 首先&#xff0c;恭喜你打开了一个系统化的学习专栏&#xff0c;在这个vue专栏中&#xff0c;大家可以根据博主发布文章的时间顺序进行一个学习。博主vue专栏指南在这&#xff1a;vue专栏的学习指南 &#x1f973;博主&#xff1a;初映CY的前说(前端领域) &#x1f…