vue指令實現組件通信的方法:1、父組件通過“props”的方式向子組件傳遞數據,而通過“$emit”子組件可以向父組件通信;2、通過“ref/$refs”屬性實現組件通信;3、通過eventBus事件總線實現組件通信;4、使用Vuex實現組件通信;5、通過“$attrs”與“$listeners”實現組件通信等。
本教程操作環境:Windows10系統、Vue 3版、DELL G3電腦
vue指令怎么實現組件通信?
Vue實現組件間通信的七種方式
1. props / $emit
父組件通過props
的方式向子組件傳遞數據,而通過$emit
子組件可以向父組件通信:
- 父傳子:父組件通過import引入子組件,并注冊,在子組件標簽上添加要傳遞的屬性,子組 件通過props接收,接收有兩種形式一是通過數組形式[‘要接收的屬性’ ],二是通過對象形式{ }
- 子傳父:父組件向子組件傳遞事件方法,子組件通過
$emit
觸發事件,回調給父組件
props的特點:
- props只能是父組件向子組件進行傳值,props使得父子組件之間形成一個單向的下行綁定。子組件的數據會隨著父組件的更新而響應式更新;但是子組件無法引起父組件的數據更新。
- props可以顯示定義一個或一個以上的數據,對于接收的數據,可以是各種數據類型,同樣也可以是傳遞一個對象或函數。
- props屬性名規則:若在props中使用駝峰形式,模板中標簽需要使用短橫線的形式來書寫。
代碼示例:
父傳子(prop的用法)
父組件:
<template> <div id="father"> <son :msg="msg" :fn="myFunc"></son> </div> </template> <script> import son from "./son.vue"; export default { name: "father", components: { son }, data() { msg: "我是父組件"; }, methods: { myFunc() { console.log("我是父組件的方法"); } } }; </script>
子組件:
<template> <div id="son"> <p>{{msg}}</p> <button @click="fn">按鈕</button> </div> </template> <script> export default { name: "son", props: ["msg", "fn"] }; </script>
子傳父($emit的用法)
$emit 綁定一個自定義事件,當這個事件被執行的時候就會將參數傳遞給父組件,而父組件通過v-on監聽并接收參數
父組件:
<template> <div id="father"> <son :arrList="arrList" @changeIndex="changeIndex"></son> <p>{{currentIndex}}</p> </div> </template> <script> import son from './son.vue' export default { name: 'father', components: { son}, data() { return { currentIndex: -1, arrList: ['龍族', '繪梨衣', '前端','后端'] } }, methods: { changeIndex(index) { this.currentIndex = index } } } </script>
子組件:
<template> <div> <div v-for="(item, index) in arrList" :key="index" @click="emitIndex(index)">{{item}}</div> </div> </template> <script> export default { props: ['arrList'], methods: { emitIndex(index) { this.$emit('changeIndex', index) // 觸發父組件的方法,并傳遞參數index } } } </script>
2.ref / $refs
ref:這個屬性用在子組件上,它的引用就指向了該子組件的實例,可以通過實例來訪問組件的數據和方法;如果在普通的 DOM 元素上使用,引用指向的就是 DOM元素。
父組件:
<template> <child ref="child"></component-a> </template> <script> import child from './child.vue' export default { components: { child }, mounted () { console.log(this.$refs.child.name); // mySon this.$refs.child.sayHello(); // Hello father! } } </script>
子組件:
<template> <div id="app"></div> </template> <script> export default { name:'child', data () { return { name: 'mySon' } }, methods: { sayHello () { console.log('Hello father!') } } } </script>
3.eventBus(事件總線)
其原理就是:事件訂閱發布,eventBus
又稱為事件總線,在vue中可以使用它來作為溝通橋梁的概念, 就像是所有組件共用相同的事件中心,可以向該中心注冊發送事件或接收事件, 所以組件都可以通知其他組件。
使用步驟如下:
(1)創建事件中心管理組件之間的通信
// event-bus.js import Vue from 'vue' export const EventBus = new Vue()
(2)發送事件 假設有兩個兄弟組件firstCom和secondCom:
firstCom和secondCom的父組件:
<template> <div> <first-com></first-com> <second-com></second-com> </div> </template> <script> import firstCom from './firstCom.vue' import secondCom from './secondCom.vue' export default { components: { firstCom, secondCom } } </script>
在firstCom組件中發送事件:
<template> <div> <button @click="add">點擊增加</button> </div> </template> <script> import {EventBus} from './event-bus.js' // 引入事件中心 export default { data(){ return{ num:0 } }, methods:{ add(){ EventBus.$emit('addition', { num:this.num++ }) } } } </script>
(3)接收事件
在secondCom組件中接收事件:
<template> <div>求和: {{count}}</div> </template> <script> import { EventBus } from './event-bus.js' export default { data() { return { count: 0 } }, mounted() { EventBus.$on('addition', param => { this.count = this.count + param.num; }) } } </script>
在上述代碼中,這就相當于將num值存貯在了事件總線中,在其他組件中可以直接訪問。事件總線就相當于一個橋梁,不用組件通過它來通信。雖然看起來比較簡單,但是這種方法也有不變之處,如果項目過大,使用這種方式進行通信,后期維護起來會很困難。
4.Vuex
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,并以相應的規則保證狀態以一種可預測的方式發生變化.
Vuex 解決了多個視圖依賴于同一狀態和來自不同視圖的行為需要變更同一狀態的問題,將開發者的精力聚焦于數據的更新而不是數據在組件之間的傳遞上
Vuex各個模塊:
- state:用于數據的存儲,是store中的唯一數據源
- getters:如vue中的計算屬性一樣,基于state數據的二次包裝,常用于數據的篩選和多個數據的相關性計算
- mutations:類似函數,改變state數據的唯一途徑,且不能用于處理異步事件
- actions:類似于mutation,用于提交mutation來改變狀態,而不直接變更狀態,可以包含任意異步操作
- modules:類似于命名空間,用于項目中將各個模塊的狀態分開定義和操作,便于維護
Vuex使用步驟:
(1)這里我們先新建 store文件夾, 對Vuex進行一些封裝處理
在 store 文件夾下添加 index.js 文件
// index.js // 自動掛載指定目錄下的store import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) let modules = {} // @/store/module 目錄下的文件自動掛載為 store 模塊 const subModuleList = require.context('@/store/modules', false, /.js$/) subModuleList.keys().forEach(subRouter => { const moduleName = subRouter.substring(2, subRouter.length - 3) modules[moduleName] = subModuleList(subRouter).default }) //也可自己手動掛載(自行選擇) export default new Vuex.Store({ state: {}, mutations: {}, actions: {}, modules })
(2)在 store 文件夾下添加 module
文件夾,在module文件夾再新建 user.js
文件
// user.js import user from '@/utils/user.js' import userApi from '@/apis/user' import { OPEN_ACCOUNT_STAGE, STAGE_STATUS } from '@/constant' let getUserPromise = null export default { namespaced: true, state() { return { userInfo: null, // 用戶信息 isLogined: !!user.getToken(), // 是否已經登錄 } }, mutations: { // 更新用戶信息 updateUser(state, payload) { state.isLogined = !!payload state.userInfo = payload }, }, actions: { // 獲取當前用戶信息 getUserInfo(context, payload) { //相關代碼 }, // 登出 logout(context, payload = {}) { // 是否手動退出 const { manual } = payload if (manual) { await userApi.postLogout() } user.clearToken() context.commit('updateUser', null) }, } }
(3)然后在項目的 main.js
文件中引入
import Vue from 'vue' import App from '@/app.vue' import { router } from '@/router' import store from '@/store/index' const vue = new Vue({ el: '#app', name: 'root', router, store, render: h => h(App), })
(4)封裝很愉快結束了了,然后就正常操作即可
this.$store.state.user.isLogined this.$store.state.user.userInfo this.$store.commit('user/updateUser', {}) await this.$store.dispatch('user/logout', { manual: true })
5.$attrs與 $listeners
現在我們來討論另一種情況:如果我們給出的組件關系圖中A組件與D組件是隔代關系, 那它們之前進行通信有哪些方式呢?
- 使用props綁定來進行一級一級的信息傳遞, 如果D組件中狀態改變需要傳遞數據給A, 使用事件系統一級級往上傳遞
- 使用eventBus,這種情況下還是比較適合使用, 但是碰到多人合作開發時, 代碼維護性較低, 可讀性也低
- 使用Vuex來進行數據管理, 但是如果僅僅是傳遞數據, 而不做中間處理,使用Vuex處理感覺有點大材小用了.
所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。
inheritAttrs
默認情況下父作用域的不被認作 props 的 attribute 綁定 (attribute bindings) 將會“回退”且作為普通的 HTML attribute 應用在子組件的根元素上。當撰寫包裹一個目標元素或另一個組件的組件時,這可能不會總是符合預期行為。
通過設置 inheritAttrs 到 false,這些默認行為將會被去掉。而通過實例 property $attrs 可以讓這些 attribute 生效,且可以通過 v-bind 顯性的綁定到非根元素上。
注意:這個選項不影響 class 和 style 綁定,Vue對class和style做了特殊處理
簡單來說就是
- inheritAttrs:true 時繼承除props之外的所有屬性
- inheritAttrs:false 只繼承class 和 style屬性
- $attrs:包含了父作用域中不被認為 (且不預期為) props 的特性綁定 (class 和 style 除外),并且可以通過 v-bind="$attrs" 傳入內部組件。當一個組件沒有聲明任何 props 時,它包含所有父作用域的綁定 (class 和 style 除外)。
- $listeners:包含了父作用域中的 (不含 .native 修飾符) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件。它是一個對象,里面包含了作用在這個組件上的所有事件監聽器,相當于子組件繼承了父組件的事件。
代碼示例:
父組件:
<template> <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" /> </template> <script> import Child from '../components/child.vue' export default { name: 'father', components: { Child }, data () { return { name: '繪梨衣', age: 22, infoObj: { from: '河北', job: 'superman', hobby: ['reading', 'writing', 'eating'] } } }, methods: { updateInfo() { console.log('update info'); }, delInfo() { console.log('delete info'); } } } </script>
兒子組件:
<template> <!-- 通過 $listeners 將父作用域中的事件,傳入 grandSon 組件,使其可以獲取到 father 中的事件 --> <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners" /> </template> <script> import GrandSon from '../components/grandSon.vue' export default { name: 'child', components: { GrandSon }, props: ['name'], data() { return { height: '170cm', weight: '55kg' }; }, created() { console.log(this.$attrs); // 結果:age, infoObj, 因為父組件共傳來name, age, infoObj三個值,由 //于name被 props接收了,所以只有age, infoObj屬性 console.log(this.$listeners); // updateInfo: f, delInfo: f }, methods: { addInfo () { console.log('add info') } } } </script>
孫子組件:
<template> <div> {{ $attrs }} --- {{ $listeners }} <div> </template> <script> export default { props: ['weight'], created() { console.log(this.$attrs); // age, infoObj, height console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f this.$emit('updateInfo') // 可以觸發 father 組件中的updateInfo函數 } } </script>
6.$parent / $children
- 使用$parent可以讓組件訪問父組件的實例(訪問的是上一級父組件的屬性和方法)。
- 使用 $children 可以讓組件訪問子組件的實例,但是, $children 并不能保證順序,并且訪問的數據也不是響應式的。
注意:
- 通過 $parent 訪問到的是上一級父組件的實例,可以使用 $root 來訪問根組件的實例
- 在組件中使用$children拿到的是所有的子組件的實例,它是一個數組,并且是無序的
- 在根組件 #app 上拿 $parent 得到的是 new Vue()的實例,在這實例上再拿 $parent 得到的是undefined,而在最底層的子組件拿 $children 是個空數組
- $children 的值是數組,而 $parent是個對象
用法:
子組件:
<template> <div> <span>{{message}}</span> <p>父組件的值為: {{parentVal}}</p> </div> </template> <script> export default { data() { return { message: 'Vue' } }, computed:{ parentVal(){ return this.$parent.msg; } } } </script>
父組件:
<template> <div class="app"> <div>{{msg}}</div> <child></child> <button @click="change">點擊改變子組件值</button> </div> </template> <script> import child from './child.vue' export default { components: { child }, data() { return { msg: 'Hello' } }, methods: { change() { // 獲取到子組件 this.$children[0].message = 'JavaScript' } } } </script>
7.依賴注入(provide / inject)
這種方式就是vue中依賴注入,該方法用于 父子組件之間 的通信。當然這里所說的父子不一定是真正的父子,也可以是祖孫組件,在層數很深的情況下,可以使用這種方式來進行傳值。就不用一層一層的傳遞數據了。
provide和inject是vue提供的兩個鉤子,和data、methods是同級的。并且provide的書寫形式和data一樣。
- provide 鉤子用來發送數據或方法
- inject鉤子用來接收數據或方法
注意: 依賴注入所提供的屬性是非響應式的。
用法:
父組件:
provide() { return { num: this.num }; }
子組件:
inject: ['num']
還有另一種寫法,這種寫法可以訪問父組件中的所有屬性:
provide() { return { app: this }; } data() { return { num: 111 }; } inject: ['app'] console.log(this.app.num)
總結
1.父子組件間通信
- 子組件通過 props 屬性來接受父組件的數據,然后父組件在子組件上注冊監聽事件,子組件通過 emit 觸發事件來向父組件發送數據。
- 通過 ref 屬性給子組件設置一個名字。父組件通過 $refs 組件名來獲得子組件,子組件通過 $parent 獲得父組件,這樣也可以實現通信。
- 使用 provide/inject,在父組件中通過 provide提供變量,在子組件中通過 inject 來將變量注入到組件中。不論子組件有多深,只要調用了 inject 那么就可以注入 provide中的數據
2.跨代組件間通信
跨代組件間通信其實就是多層的父子組件通信,同樣可以使用上述父子組件間通信的方法,只不過需要多層通信會比較麻煩。
3.兄弟組件間通信
通過 $parent + $refs 以父組件為中間人來獲取到兄弟組件,也可以進行通信。
4.任意組件間通信
使用 eventBus ,其實就是創建一個事件中心,相當于中轉站,可以用它來傳遞事件和接收事件。它的本質是通過創建一個空的 Vue 實例來作為消息傳遞的對象,通信的組件引入這個實例,通信的組件通過在這個實例上監聽和觸發事件,來實現消息的傳遞。
推薦學習:《vue.js視頻教程》