vue嵌套組件傳參實(shí)例分享
前言:
假設(shè)我們已經(jīng)了解vue組件常見的有父子組件通信,兄弟組件通信。而父子組件通信很簡單,父組件會通過 props 向下傳數(shù)據(jù)給子組件,當(dāng)子組件有事情要告訴父組件時會通過 $emit 事件告訴父組件。那么當(dāng)兩個組件之間不是父子關(guān)系,怎樣傳遞數(shù)據(jù)呢?
先來看一下這個例子:
遞歸嵌套組件參數(shù)傳遞
我們封裝了一個名為 NestedDir 的子組件(嵌套目錄的意思),內(nèi)容如下(用到了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 數(shù)組類型的數(shù)據(jù),并且它的內(nèi)部點(diǎn)擊 新增目錄、新增文件,可以觸發(fā) 父級 監(jiān)聽的 change 事件。比較特殊的是這個組件中調(diào)用了自己:
<NestedDir v-if="el.children" :nested="el.children"/>
不過要注意的是調(diào)用自己的時候我們并沒有在它上面監(jiān)聽它內(nèi)部傳來的change事件,這也是導(dǎo)致二級目錄點(diǎn)擊新增按鈕無效的原因。
我們傳遞給它的 nested 數(shù)據(jù)結(jié)構(gòu)大概是下面的樣子:
[{
"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": []
}]父組件中調(diào)用 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>渲染出的頁面是下面的樣子:

深層遞歸組件事件丟失
我們構(gòu)造出了一個理論上可以無限嵌套的目錄結(jié)構(gòu),但是經(jīng)過測試發(fā)現(xiàn) 在二級目錄上的 新增按鈕 點(diǎn)擊是沒有任何反應(yīng)的,原因是我們在 NestedDir 中調(diào)用了它自己并沒有監(jiān)聽內(nèi)部的change事件(上邊提到過),所以它無法觸發(fā) 父級的-父級 的監(jiān)聽事件。
如何解決?
- 在遞歸調(diào)用的時候也監(jiān)聽一下change事件,并間接傳遞到最外層組件(這個是最容易想到的方法,但是如果組件嵌套很深,簡直就是個噩夢)
- EventBus(事件總線)
EventBus
什么事EventBus?
它其實(shí)就是一個Vue實(shí)例,有$emit、$on、$off方法,允許從一個組件向另一組件傳遞數(shù)據(jù),而不需要借助父組件。具體做法是在一個組件$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";
在頁面創(chuàng)建時加入事件監(jiān)聽,銷毀時移除事件監(jiān)聽:
created() {
eventBus.$on('change', function (data) {
this.handleChange(data)
})
},
destroyed() {
eventBus.$off('change')
}NestedDir.vue 中也需要做相應(yīng)改動,只需要修改methods中的add方法:
import { eventBus } from "../main";
//...略
methods: {
add(el) {
// this.$emit('change', el)
eventBus.$emit('change', el)
}
}這樣點(diǎn)擊二級目錄的新增按鈕,就可以正常觸發(fā)彈出框了。
上面的eventBus只在Vue2中有效,Vue3中已經(jīng)移除了$on, $off 這些方法,所以下一篇文章打算自己做一個Vue的插件來處理這種類似于Pub/Sub的情況。
到此這篇關(guān)于vue嵌套組件傳參實(shí)例分享的文章就介紹到這了,更多相關(guān)vue嵌套組件傳參內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vite+vue3+element-plus項(xiàng)目搭建的方法步驟
因?yàn)関ue3出了一段時間了,element也出了基于vue3.x版本的element-plus,vite打包聽說很快,嘗試一下,感興趣的可以了解一下2021-06-06
在vs code 中如何創(chuàng)建一個自己的 Vue 模板代碼
這篇文章主要介紹了在vs code 中如何創(chuàng)建一個自己的 Vue 模板代碼,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-11-11
vue中實(shí)現(xiàn)多頁面應(yīng)用方式
這篇文章主要介紹了vue中實(shí)現(xiàn)多頁面應(yīng)用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
element-ui 實(shí)現(xiàn)響應(yīng)式導(dǎo)航欄的示例代碼
這篇文章主要介紹了element-ui 實(shí)現(xiàn)響應(yīng)式導(dǎo)航欄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

