vue嵌套組件傳參實例分享
前言:
假設(shè)我們已經(jīng)了解vue組件常見的有父子組件通信,兄弟組件通信。而父子組件通信很簡單,父組件會通過 props 向下傳數(shù)據(jù)給子組件,當子組件有事情要告訴父組件時會通過 $emit 事件告訴父組件。那么當兩個組件之間不是父子關(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)部點擊 新增目錄、新增文件,可以觸發(fā) 父級 監(jiān)聽的 change 事件。比較特殊的是這個組件中調(diào)用了自己:
<NestedDir v-if="el.children" :nested="el.children"/>
不過要注意的是調(diào)用自己的時候我們并沒有在它上面監(jiān)聽它內(nèi)部傳來的change事件,這也是導致二級目錄點擊新增按鈕無效的原因。
我們傳遞給它的 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) 在二級目錄上的 新增按鈕 點擊是沒有任何反應的,原因是我們在 NestedDir 中調(diào)用了它自己并沒有監(jiān)聽內(nèi)部的change事件(上邊提到過),所以它無法觸發(fā) 父級的-父級 的監(jiān)聽事件。
如何解決?
- 在遞歸調(diào)用的時候也監(jiān)聽一下change事件,并間接傳遞到最外層組件(這個是最容易想到的方法,但是如果組件嵌套很深,簡直就是個噩夢)
- EventBus(事件總線)
EventBus
什么事EventBus?
它其實就是一個Vue實例,有$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 中也需要做相應改動,只需要修改methods中的add方法:
import { eventBus } from "../main"; //...略 methods: { add(el) { // this.$emit('change', el) eventBus.$emit('change', el) } }
這樣點擊二級目錄的新增按鈕,就可以正常觸發(fā)彈出框了。
上面的eventBus只在Vue2中有效,Vue3中已經(jīng)移除了$on, $off 這些方法,所以下一篇文章打算自己做一個Vue的插件來處理這種類似于Pub/Sub的情況。
到此這篇關(guān)于vue嵌套組件傳參實例分享的文章就介紹到這了,更多相關(guān)vue嵌套組件傳參內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于vuejs中v-if和v-show的區(qū)別及v-show不起作用問題
v-if 有更高的切換開銷,而 v-show 有更高的出事渲染開銷.因此,如果需要非常頻繁的切換,那么使用v-show好一點;如果在運行時條件不太可能改變,則使用v-if 好點2018-03-03vue使用$emit時,父組件無法監(jiān)聽到子組件的事件實例
下面小編就為大家分享一篇vue使用$emit時,父組件無法監(jiān)聽到子組件的事件實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-02-02vue proxyTable 接口跨域請求調(diào)試的示例
本篇文章主要介紹了vue proxyTable 接口跨域請求調(diào)試的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09vue中如何使用echarts和echarts-gl實現(xiàn)3D餅圖環(huán)形餅圖
現(xiàn)在vue是很多公司前端的主流框架,我目前所在公司接觸的項目也都是使用vue來實現(xiàn)的,很少有完全使用原生的JavaScript來寫項目的了,下面這篇文章主要給大家介紹了關(guān)于vue中如何使用echarts和echarts-gl實現(xiàn)3D餅圖環(huán)形餅圖的相關(guān)資料,需要的朋友可以參考下2023-03-03