雖然事件總線最初似乎是組件間通信的便捷方法,但建議探索替代選項,例如provide/inject或全局狀態管理。這些替代方案為促進組件之間的通信提供了更強大且可維護的解決方案。
在Vue.js中有一種使用event emitters
通過父組件在兩個子組件之間進行通信的方法。在子組件中設置事件并在父組件中設置偵聽器時,響應式將通過父組件向下傳遞到嵌套組件。
雖然這是一個有價值的解決方案,但隨著項目的發展,它可能會變得笨拙。事件總線是一個 Vue.js 實例,它可以在一個組件中發出事件,然后直接偵聽并響應另一個組件中發出的事件——無需父組件的幫助。事件總線比發射器更高效,因為它需要更少的代碼來運行。
在本教程中,我們將在 Vue.js 項目中創建一個事件總線,以促進兩個組件之間通過私有通道進行通信。這通常稱為發布-訂閱方法。
先決條件
這篇文章適合所有階段的開發人員,包括初學者。在閱讀本文之前,您應該已經具備以下幾點:
- 已安裝版本 14.18+ 及更高版本的Node.js。您可以通過在終端/命令提示符下運行以下命令來驗證您是否具有此版本:
node -v
- 安裝了 npm 版本 6.x 及更高版本。在終端中使用以下命令驗證安裝的版本:
npm -v
- Visual Studio Code Editor 或類似的代碼編輯器
- 運行以下命令來搭建 Vite 和 Vue 項目的基架:
# npm 6.x
npm create vite@latest event-bus-tutorial --template vue
# npm 7+, extra double-dash is needed:
npm create vite@latest event-bus-tutorial -- --template vue
- 導航到 event-bus-tutorial 目錄并使用 npm 安裝所需的依賴項:
cd event-bus-tutorial
npm install
在 Vue 2 中使用事件總線模式
在 Vue 2.x 中,Vue 實例可用于觸發通過事件發射器 API($on
、$off
和$once
) 強制附加的處理程序。
為了使用事件總線模式,你所要做的就是創建一個 Vue 構造函數的新實例,將該實例分配給一個名為 eventBus
的常量變量,然后將其導出。這個實例充當事件總線,允許 Vue 應用程序中的不同組件相互通信:
// eventBus.js
const eventBus = new Vue()
export default eventBus
導入 eventBus 實例并在要接收事件的組件中,添加事件偵聽器:
// ChildComponent.vue
import eventBus from './eventBus'
export default {
mounted() {
// adding eventBus listener
eventBus.$on('custom-event', () => {
console.log('Custom event triggered!')
})
},
beforeDestroy() {
// removing eventBus listener
eventBus.$off('custom-event')
}
}
導入eventBus
實例并在要發送事件的組件中,添加事件發射器:
// ParentComponent.vue
import eventBus from './eventBus'
export default {
methods: {
sendCustomEvent() {
// sending the event
eventBus.$emit('custom-event')
}
}
}
Vue 3 中的事件總線入門
在 Vue 3 中, $on
、 $off
和 $once
方法已從 Vue 實例中完全刪除。因此,為了使用事件總線模式,必須安裝外部事件發射器和偵聽器包,例如 mitt
。
使用以下命令安裝 mitt
包并開始提供應用程序:
npm install --save mitt
npm run dev
mAIn.js 打開位于目錄 src 中的文件,并修改其中的代碼,如下所示:
import { createApp } from 'vue'
import mitt from 'mitt'
import App from './App.vue'
const emitter = mitt()
const app = createApp(App)
app.config.globalProperties.emitter = emitter
app.mount('#app')
這段代碼通過創建一個發射器實例,使其通過 Vue 應用程序實例全局訪問,并將應用程序掛載到 DOM 中,將 mitt 事件發射器庫與 Vue 3 應用程序集成。這允許組件使用發射器發出和偵聽事件,而無需直接導入。
創建我們的子組件
在我們的演示中,我們將創建兩個子組件,它們需要在不使用父組件作為中介的情況下相互通信。首先,在 components
目錄中創建一個名為Child1.vue
的新文件,并將以下代碼塊粘貼到其中:
<template>
<div>
<button>Increment</button>
</div>
</template>
<script >
export default {
name: "Child1",
data: () => ({
counter: 0,
}),
methods: {},
};
</script>
<style scoped>
button {
margin: 4rem 0 0;
padding: 1rem;
background-color: #0d6efd;
color: white;
border-radius: 1rem;
font-size: 2rem;
}
</style>
總體而言,此代碼呈現一個 <div>
包含樣式的 <button>
。該組件還具有內部數據屬性counter
設置為0和空methods
屬性。
在components
目錄中創建一個名為Child2.vue
的新文件,并在其中粘貼以下代碼塊:
<template>
<div>
<h1>{{ counter }}</h1>
</div>
</template>
<script>
export default {
name: "Child2",
data: () => ({
counter: 0,
}),
};
</script>
<style scoped>
h1 {
margin: 5rem 0 0;
font-size: 10.5rem;
}
</style>
這個 Vue 組件渲染一個 <div>
包含樣式標題 <h1>
的組件。該 counter
值動態顯示在標題中。
現在,轉到您的App.vue
文件并將其整個代碼替換為以下內容:
<template>
<div id="app">
<Child2 />
<Child1 />
</div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
export default {
name: "app",
components: {
Child1,
Child2,
},
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 5rem;
}
</style>
在 App.vue
組件內部,代碼導入并呈現樣式化的父 div 中的 Child1
和Child2
組件。
設置事件
現在,兩個組件已準備就緒,您可以在組件中偵聽 Child2
事件時,通過在 Child1
組件中發出來設置事件。打開文件 Child1.vue
并將以下代碼塊復制到其中:
<template>
<div>
<button v-on:click="sendEvent">Increment</button>
</div>
</template>
<script >
export default {
name: "Child1",
data: () => ({
counter: 0,
}),
methods: {
sendEvent() {
this.counter += 1;
this.emitter.emit("increment", { msg: this.counter });
},
},
};
</script>
<style scoped>
button {
margin: 4rem 0 0;
padding: 1rem;
background-color: #0d6efd;
color: white;
border-radius: 1rem;
font-size: 2rem;
}
</style>
在這里,onclick
事件偵聽器被添加到 button
元素中。觸發此事件時,將調用名為 sendEvent()
的方法。
該方法 sendEvent()
將計數器值遞增 1,并使用全局事件發射器向任何父組件或同級組件發出具有更新 counter
值的事件 increment
。
監聽和響應事件
設置事件后,我們需要讓第二個組件偵聽并響應事件。打開文件 Child2.vue
并復制到以下代碼塊中:
<template>
<div>
<h1>{{ counter }}</h1>
</div>
</template>
<script>
export default {
name: "Child2",
data: () => ({
counter: 0,
}),
mounted() {
this.emitter.on("increment", (data) => {
this.counter = data.msg;
});
},
};
</script>
<style scoped>
h1 {
margin: 5rem 0 0;
font-size: 10.5rem;
}
</style>
該代碼使用mounted
生命周期掛鉤在應用程序掛載到 DOM 上時初始化偵聽過程。
該emitter.on
語句現在正在偵聽 increment
事件,向下傳遞data
參數,并將其設置為新計數器:
單擊組件上的“Increment”按鈕時, increment
事件將與更新 counter
的值一起發送到 Child2
和 Child1
組件。
Vue.js中事件總線的當前狀態
在大多數情況下,不建議使用全局事件總線來促進組件之間的通信。雖然它最初似乎是最簡單的解決方案,但隨著時間的推移,它經常導致重大的維護挑戰。
根據具體情況,Vue 開發團隊推薦了幾種替代方法,而不是依賴事件總線:
- 優先使用 props 和事件作為父組件和子組件之間通信的主要方式。如有必要,兄弟姐妹可以通過其共同的父母進行交流
- 使用provide/inject允許組件及其插槽內容之間的通信。這對于始終一起使用的緊密耦合組件特別有用
- provide/inject還可以促進組件層次結構中相距甚遠的組件之間的通信。它可以幫助消除對props傳遞過深的需求,其中props通過多個級別的組件向下傳遞,而這些組件本身不需要這些props本身。
- 為避免props傳遞過深,請考慮重構代碼庫以利用slot。如果中間組件不需要某些props,則可能表明存在責任分離問題。在該組件中引入插槽使父組件能夠直接提供內容,從而允許傳遞 props 而不涉及中間組件
- 像 Pinia 這樣的全局狀態管理庫可用于跨組件管理全局狀態。這些庫提供了一種集中的狀態管理方法,允許組件訪問和修改共享狀態,而無需顯式傳遞 prop
總結
這是對 Vue.js 中事件總線的介紹。事件總線充當組件之間易于實現的獨立通信,無需通過中央組件或父組件。
雖然事件總線最初似乎是組件間通信的便捷方法,但建議探索替代選項,例如provide/inject或全局狀態管理。這些替代方案為促進組件之間的通信提供了更強大且可維護的解決方案。