vue嵌套组件传参实例分享
作者:???????老毛 发布时间:2024-06-07 15:23:11
前言:
假设我们已经了解vue组件常见的有父子组件通信,兄弟组件通信。而父子组件通信很简单,父组件会通过 props 向下传数据给子组件,当子组件有事情要告诉父组件时会通过 $emit 事件告诉父组件。那么当两个组件之间不是父子关系,怎样传递数据呢?
先来看一下这个例子:
递归嵌套组件参数传递
我们封装了一个名为 NestedDir
的子组件(嵌套目录的意思),内容如下(用到了element ui组件):
<!-- NestedDir.vue -->
<template>
<ul class="nest_wrapper">
<li v-for="(el, index) in nested" :key="index">
<div v-if="el.type ==='dir'" class="dir">
<p>{{el.name}}</p>
<div class="btn_group">
<el-button type="warning" size="mini" @click="add({id: el.id, type: 'dir'})">新增目录</el-button>
<el-button type="warning" size="mini" @click="add({id: el.id, type: 'file'})">新增文件</el-button>
</div>
</div>
<div v-if="el.type ==='file'" class="file">
<p>{{el.name}}</p>
</div>
<NestedDir v-if="el.children" :nested="el.children"/>
</li>
</ul>
</template>
<script>
export default {
name: "NestedDir",
props: {
nested: {
type: Array,
}
},
methods: {
add(el) {
this.$emit('change', el)
}
}
}
</script>
可以看出这个 NestedDir 接收父级传来的 nested 数组类型的数据,并且它的内部点击 新增目录、新增文件,可以触发 父级 监听的 change 事件。比较特殊的是这个组件中调用了自己:
<NestedDir v-if="el.children" :nested="el.children"/>
不过要注意的是调用自己的时候我们并没有在它上面监听它内部传来的change事件,这也是导致二级目录点击新增按钮无效的原因。
我们传递给它的 nested 数据结构大概是下面的样子:
[{
"id": 1,
"name": "目录1",
"type": "dir",
"children": [{
"id": 2,
"name": "目录3",
"type": "dir",
"children": [],
"pid": 1
}, {
"id": 3,
"name": "文件2",
"type": "file",
"pid": 1
}]
}, {
"id": 4,
"name": "目录2",
"type": "dir",
"children": []
}, {
"id": 5,
"name": "文件1",
"type": "file",
"children": []
}]
父组件中调用 NestedDir:
<!-- directory.vue -->
<template>
<div style="width: 50%;box-shadow: 0 0 4px 2px rgba(0,0,0,.1);margin: 10px auto;padding-bottom: 10px;">
<!-- 顶部按钮组 -->
<div class="btn_group">
<el-button type="warning" size="mini" @click="showDialog({type: 'dir'})">新增目录</el-button>
<el-button type="warning" size="mini" @click="showDialog({type: 'file'})">新增文件</el-button>
</div>
<!-- 嵌套组件 -->
<NestedDir :nested="catalog" @change="handleChange"/>
<!-- 新增弹出框 -->
<el-dialog :title="title" :visible.sync="dialogFormVisible" width="300px">
<el-form :model="form">
<el-form-item label="名称">
<el-input v-model="form.name" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="dialogFormVisible = false">取 消</el-button>
<el-button type="primary" @click="confirm">确 定</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import NestedDir from "./NestedDir";
export default {
name: "directory",
components: {
NestedDir
},
created() {
this.catalog = this.getTree()
},
computed: {
maxId() {
return this.arr.lastIndex + 2
},
topNodes() {
this.arr.forEach(item => {
if (item.children) item.children = []
})
return this.arr.filter(item => !item.pid)
}
},
data() {
return {
arr: [
{id: 1, name: '目录1', type: 'dir', children: []},
{id: 2, name: '目录3', type: 'dir', children: [], pid: 1},
{id: 3, name: '文件2', type: 'file', pid: 1},
{id: 4, name: '目录2', type: 'dir', children: []},
{id: 5, name: '文件1', type: 'file'},
],
title: '',
dialogFormVisible: false,
form: {
id: '',
name: '',
type: '',
pid: ''
},
catalog: []
}
},
methods: {
handleChange(el) {
this.showDialog(el)
},
confirm() {
this.arr.push({...this.form})
this.dialogFormVisible = false
this.catalog = this.getTree()
this.form = {
id: '',
name: '',
type: '',
pid: '' , // 父级的id
}
},
showDialog(el) {
if (el.type === 'dir') {
this.title = '新增目录'
this.form.children = []
this.form.type = 'dir'
} else {
this.title = '新增文件'
this.form.type = 'file'
}
if (el.id) {
this.form.pid = el.id
this.form.id = this.maxId
} else {
this.form.id = this.maxId
}
this.dialogFormVisible = true
},
getTree() {
this.topNodes.forEach(node => {
this.getChildren(this.arr, node.children, node.id)
})
return this.topNodes
},
getChildren(data, result, pid) {
for (let item of data) {
if (item.pid === pid) {
const newItem = {...item, children: []}
result.push(newItem)
this.getChildren(data, newItem.children, item.id)
}
}
}
}
}
</script>
<style scoped>
.btn_group {
padding: 20px 10px;
background-color: rgba(87, 129, 189, 0.13);
}
</style>
渲染出的页面是下面的样子:
深层递归组件事件丢失
我们构造出了一个理论上可以无限嵌套的目录结构,但是经过测试发现 在二级目录上的 新增按钮 点击是没有任何反应的,原因是我们在 NestedDir 中调用了它自己并没有监听内部的change事件(上边提到过),所以它无法触发 父级的-父级 的监听事件。
如何解决?
在递归调用的时候也监听一下change事件,并间接传递到最外层组件(这个是最容易想到的方法,但是如果组件嵌套很深,简直就是个噩梦)
EventBus(事件总线)
EventBus
什么事EventBus?
它其实就是一个Vue实例,有$emit、$on、$off方法,允许从一个组件向另一组件传递数据,而不需要借助父组件。具体做法是在一个组件$emit,在另一个组件$on,可以像下面这样做:
// main.js
import Vue from 'vue'
import App from './App.vue'
export const eventBus = new Vue(); // creating an event bus.
new Vue({
render: h => h(App),
}).$mount('#app')
这样我们来改造一下 directory.vue,只需要改动srcipt部分:
<script>
import NestedDir from "./NestedDir";
import { eventBus } from "../main";
export default {
name: "directory",
components: {
NestedDir
},
created() {
this.catalog = this.getTree()
eventBus.$on('change', function (data) {
// todo 向之前一样处理即可
})
},
destroyed() {
eventBus.$off('change')
},
computed: {
maxId() {
return this.arr.lastIndex + 2
}
},
data() {
return {
arr: [
{id: 1, name: '目录1', type: 'dir', children: []},
{id: 2, name: '目录3', type: 'dir', children: [], pid: 1},
{id: 3, name: '文件2', type: 'file', pid: 1},
{id: 4, name: '目录2', type: 'dir', children: []},
{id: 5, name: '文件1', type: 'file'},
],
title: '',
dialogFormVisible: false,
form: {
id: '',
name: '',
type: '',
pid: ''
},
catalog: []
}
},
methods: {
handleChange(el) {
this.showDialog(el)
},
confirm() {
this.arr.push({...this.form})
this.dialogFormVisible = false
this.catalog = this.getTree()
this.form = {
id: '',
name: '',
type: '',
pid: '' , // 父级的id
}
},
showDialog(el) {
if (el.type === 'dir') {
this.title = '新增目录'
this.form.children = []
this.form.type = 'dir'
} else {
this.title = '新增文件'
this.form.type = 'file'
}
if (el.id) {
this.form.pid = el.id
this.form.id = this.maxId
} else {
this.form.id = this.maxId
}
this.dialogFormVisible = true
},
getTree() {
this.topNodes.forEach(node => {
this.getChildren(this.arr, node.children, node.id)
})
return this.topNodes
},
getChildren(data, result, pid) {
for (let item of data) {
if (item.pid === pid) {
const newItem = {...item, children: []}
result.push(newItem)
this.getChildren(data, newItem.children, item.id)
}
}
}
}
}
</script>
引入了import { eventBus } from "../main";
在页面创建时加入事件监听,销毁时移除事件监听:
created() {
eventBus.$on('change', function (data) {
this.handleChange(data)
})
},
destroyed() {
eventBus.$off('change')
}
NestedDir.vue 中也需要做相应改动,只需要修改methods中的add方法:
import { eventBus } from "../main";
//...略
methods: {
add(el) {
// this.$emit('change', el)
eventBus.$emit('change', el)
}
}
这样点击二级目录的新增按钮,就可以正常触发弹出框了。
上面的eventBus只在Vue2中有效,Vue3中已经移除了$on, $off 这些方法,所以下一篇文章打算自己做一个Vue的插件来处理这种类似于Pub/Sub的情况。
来源:https://segmentfault.com/a/1190000041677668


猜你喜欢
- 在使用Python做socket编程时,由于需要使用阻塞(默认)的方式来读取数据流,此时对于数据的结束每次都需要自己处理,太麻烦。并且网上也
- 一、先来看备份mysql数据库的命令mysqldump -u root --password=root --database abcData
- 这个项目到一开始的kickoff到现在,持续了很长的一段时间,现在差不多也接近了尾声,所以要好好做个总结,下面不会设计到太多技术层面上的东西
- 在python中使用open函数对文件进行处理。1.open()python打开文件使用open()函数,返回一个指向文件的指针。该函数常用
- 本文实例为大家分享了python3.6.1安装教程,供大家参考,具体内容如下1、安装编译环境所需包#yum install zlib-dev
- 代码如下所示:表landundertake结构如下所示:表appraiser结构如下所示:access代码:代码如下:TRANSFORM F
- 前言有时候正则表达式不只是匹配一下什么数字/邮箱/身份证/日期等等,还需要匹配某一段文字,并按照既定格式提取其中的某些值.场景一:提取SAM
- 用Pyinstaller封装一个py文件时,过程没有问题,但打开后会出现闪退的现象。因此上网找,找到了上面网址所示的解决方法。也就是用cmd
- 前言SciPy 是一个开源的 Python 算法库和数学工具包, Scipy 是基于 Numpy 的科学计算库,用于数学、科学、工程学等领域
- 看了好多登录注册和注销的教程,很乱,很迷,然后总结了一下,简单的做了一个登录,注册和注销的页面。1,首先,使用pycharm创建一个项目单击
- 最近收到一个朋友委托的需求,要将MP3的音频格式转换成wav的音频格式。于是,使用python写了这个小工具便于批量进行转换操作。首先,下载
- 函数的返回值返回结果要怎么做,多个结果又要怎么做# 函数返回值# 概念:函数执行完以后会返回一个对象,如果在函数内部有return 就可以返
- Google Chrome,又喜欢逛豆瓣,Google Chrome支持User Scripts了,但是我写的那几个插件在Chrome下又跑
- 官方文档:http://www.layui.com/demo/layedit.html官网文档目前是不支持layedit扩展式的。自己手动实
- 摆线最简单的旋轮线就是摆线,指圆在直线上滚动时,圆周上某定点的轨迹。设圆的半径为 r ,在x轴上滚动 x距离则意味着旋转了 x
- 介绍我们一起来做个示例,在.NET中新建一个类,并在这个类里新建一个方法,然后在SQL Server中调用这个方法。按照微软所述,通过宿主
- 观看本文前最好有一定的Linux命令基础,具体为centos7.3环境中清除使用yum安装的Mysql卸载前请先关闭Mysql服务servi
- 装饰器其实一直是我的一个"老大难"。这个知识点就放在那,但是拖延症。。。其实在平常写写脚本的过程中,这个知识点你可能用到
- 概述结构体是由一系列具有相同类型或不同类型的数据构成的数据集合语法定义结构体【标识自定义结构体的名称,在同一个包内不能重复】type 结构名
- 应用场景在嵌入式开发中,常常需要将一个binary文件分割成多个文件,或者将一个binary的某块区域抓成一个单独文件。本篇blog以pyt