Vue
中組件間通訊包括父子組件、兄弟組件、隔代組件之間通訊。javascript
這種組件通訊的方式是咱們運用的很是多的一種,props
以單向數據流的形式能夠很好的完成父子組件的通訊,所謂單向數據流,就是數據只能經過props
由父組件流向子組件,而子組件並不能經過修改props
傳過來的數據修改父組件的相應狀態,全部的prop
都使得其父子prop
之間造成了一個單向下行綁定,父級prop
的更新會向下流動到子組件中,可是反過來則不行,這樣會防止從子組件意外改變父級組件的狀態,致使難以理解數據的流向而提升了項目維護難度。實際上若是傳入一個基本數據類型給子組件,在子組件中修改這個值的話Vue
中會出現警告,若是對於子組件傳入一個引用類型的對象的話,在子組件中修改是不會出現任何提示的,這兩種狀況都屬於改變了父子組件的單向數據流,是不符合可維護的設計方式的。
正由於這個特性,而咱們會有須要更改父組件值的需求,就有了對應的$emit
,當咱們在組件上定義了自定義事件,事件就能夠由vm.$emit
觸發,回調函數會接收全部傳入事件觸發函數的額外參數,$emit
實際上就是是用來觸發當前實例上的事件,對此咱們能夠在父組件自定義一個處理接受變化狀態的邏輯,而後在子組件中如若相關的狀態改變時,就觸發父組件的邏輯處理事件。html
父組件向子組件傳值經過prop
傳遞值便可。vue
<!-- 子組件 --> <template> <div> <div>我是子組件,接收:{{ msg }}</div> </div> </template> <script> export default { name: "child", components: {}, props: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父組件 --> <template> <div> <child :msg="msg"></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "父組件 Msg" }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
子組件向父組件傳值須要經過事件的觸發,將更改值的行爲傳遞到父組件去執行。java
<!-- 子組件 --> <template> <div> <div>我是子組件,接收:{{ msg }}</div> <button @click="$emit('changeMsg', '子組件傳值 Msg')">觸發事件並傳遞值到父組件</button> </div> </template> <script> export default { name: "child", components: {}, props: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父組件 --> <template> <div> <child :msg="msg" @changeMsg="changeMsg" ></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "父組件 Msg" }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
v-model
一般稱爲數據雙向綁定,也能夠稱得上是一種父子組件間傳值的方式,是當前組件與input
等組件進行父子傳值,其本質上就是一種語法糖,經過props
以及input
(默認狀況下)的事件的event
中攜帶的值完成,咱們能夠自行實現一個v-model
。git
<template> <div> <div>{{msg}}</div> <input :value="msg" @input="msg = $event.target.value"> </div> </template> <script> export default { data: () => ({ msg: "Msg" }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
sync
修飾符也能夠稱爲一個語法糖,在Vue 2.3
以後新的.sync
修飾符所實現的已經再也不像Vue 1.0
那樣是真正的雙向綁定,而是和v-model
相似,是一種語法糖的形式,也能夠稱爲一種縮寫的形式,在下面父組件兩種寫法是徹底等同的。github
<!-- 子組件 --> <template> <div> <div>我是子組件,接收:{{ msg }}</div> <button @click="$emit('update:msg', '子組件傳值 Msg')">觸發事件並傳遞值到父組件</button> </div> </template> <script> export default { name: "child", components: {}, props: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父組件 --> <template> <div> <child :msg="msg1" @update:msg="msg1 = $event" ></child> <child :msg.sync="msg2" ></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg1: "父組件 Msg1", msg2: "父組件 Msg2", }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
相似於React
的Context API
,在父組件中經過provider
來提供屬性,而後在子組件中經過inject
來注入變量,不論子組件有多深,只要調用了inject
那麼就能夠注入在provider
中提供的數據,而不是侷限於只能從當前父組件的prop
屬性來獲取數據,只要在父組件內的數據,子組件均可以調用。固然Vue
中註明了provide
和inject
主要在開發高階插件/
組件庫時使用,並不推薦用於普通應用程序代碼中。vuex
<!-- 子組件 --> <template> <div> <div>inject: {{msg}}</div> </div> </template> <script> export default { name: "child", inject: ["msg"], data: () => ({ }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<template> <div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ }), provide: { msg: "provide msg" }, beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
這種組件通訊的方式適合直接的父子組件,假設此時咱們有三個組件分別爲A
、B
、C
,父組件A
下面有子組件B
,父組件B
下面有子組件C
,這時若是組件A
直接想傳遞數據給組件C
那就不能直接傳遞了,只能是組件A
經過props
將數據傳給組件B
,而後組件B
獲取到組件A
傳遞過來的數據後再經過props
將數據傳給組件C
,固然這種方式是很是複雜的,無關組件中的邏輯業務增多了,代碼維護也沒變得困難,再加上若是嵌套的層級越多邏輯也複雜,無關代碼越多,針對這樣一個問題,Vue 2.4
提供了$attrs
和$listeners
來實現可以直接讓組件A
直接傳遞消息給組件C
。segmentfault
<!-- 子子組件 --> <template> <div> </div> </template> <script> export default { name: "child-child", components: {}, data: () => ({ }), beforeCreate: function() {}, created: function() { console.log(this.$attrs); // {param: 1, test: 2} console.log(this.$listeners); // {testEvent: ƒ} }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 子組件 --> <template> <div> <!-- 直接將剩餘的參數傳遞給子組件 --> <child-child v-bind="$attrs" v-on="$listeners"></child-child> </div> </template> <script> import childChild from "./child-child"; export default { name: "child", components: { childChild }, props: ["msg"], // 聲明瞭接收名爲msg的prop 此時在此組件的$attrs則不會再有msg參數 data: () => ({ }), inheritAttrs: false, // 默認設置爲true也可 // 默認狀況下true 父做用域的不被認做 props 的 attribute 綁定將會回退且做爲普通的 HTML attribute 應用在子組件的根元素上。 beforeCreate: function() {}, created: function() { console.log(this.$attrs); // {param: 1, test: 2} console.log(this.$listeners); // {testEvent: ƒ} }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父組件 --> <template> <div> <child :msg="msg" :param="1" :test="2" @testEvent="tips" ></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "Msg", }), beforeCreate: function() {}, created: function() {}, filters: {}, computed: {}, methods: { tips: function(...args){ console.log(args); } } } </script> <style scoped> </style>
這種方式就比較直觀了,直接操做父子組件的實例,$parent
就是父組件的實例對象,而$children
就是當前實例的直接子組件實例數組了,官方文檔的說明是子實例能夠用this.$parent
訪問父實例,子實例被推入父實例的$children
數組中,節制地使用$parent
和$children
它們的主要目的是做爲訪問組件的應急方法,更推薦用props
和events
實現父子組件通訊。此外在Vue2
以後移除的$dispatch
和$broadcast
也能夠經過$children
與$parent
進行實現,固然不推薦這樣作,官方推薦的方式仍是更多簡明清晰的組件間通訊和更好的狀態管理方案如Vuex
,實際上不少開源框架都仍是本身實現了這種組件通訊的方式,例如Mint UI
、Element UI
和iView
等。數組
<!-- 子組件 --> <template> <div> </div> </template> <script> export default { name: "child", data: () => ({ }), beforeCreate: function() {}, mounted: function() { console.log(this.$parent); // VueComponent {_uid: 2, ...} console.log(this.$children); // [] }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
<!-- 父組件 --> <template> <div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ }), beforeCreate: function() {}, mounted: function() { console.log(this.$parent); // VueComponent {_uid: 1, ...} console.log(this.$children); // [VueComponent] }, filters: {}, computed: {}, methods: {} } </script> <style scoped> </style>
在項目規模不大的狀況下,徹底可使用中央事件總線EventBus
的方式,EventBus
能夠比較完美地解決包括父子組件、兄弟組件、隔代組件之間通訊,實際上就是一個觀察者模式,觀察者模式創建了一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應作出反應。因此發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間沒有相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展。首先咱們須要實現一個訂閱發佈類,並做爲全局對象掛載到Vue.prototype
,做爲Vue
實例中可調用的全局對象使用,此外務必注意在組件銷燬的時候卸載訂閱的事件調用,不然會形成內存泄漏。app
// 實現一個PubSub模塊 var PubSub = function() { this.handlers = {}; } PubSub.prototype = { on: function(key, handler) { // 訂閱 if (!(key in this.handlers)) this.handlers[key] = []; this.handlers[key].push(handler); }, off: function(key, handler) { // 卸載 const index = this.handlers[key].findIndex(item => item === handler); if (index < 0) return false; if (this.handlers[key].length === 1) delete this.handlers[key]; else this.handlers[key].splice(index, 1); return true; }, commit: function(key, ...args) { // 觸發 if (!this.handlers[key]) return false; this.handlers[key].forEach(handler => handler.apply(this, args)); return true; }, } export { PubSub } export default { PubSub }
<!-- 子組件 --> <template> <div> <div>{{msg}}</div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "init" }), beforeCreate: function() {}, created: function() { this.eventBus.on("ChangeMsg", this.changeMsg); }, beforeDestroy: function(){ this.eventBus.off("ChangeMsg", this.changeMsg); }, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
<!-- 父組件 --> <template> <div> <div>{{msg}}</div> <child></child> </div> </template> <script> import child from "./child"; export default { components: { child }, data: () => ({ msg: "init" }), beforeCreate: function() {}, created: function() { this.eventBus.on("ChangeMsg", this.changeMsg); }, beforeDestroy: function(){ this.eventBus.off("ChangeMsg", this.changeMsg); }, filters: {}, computed: {}, methods: { changeMsg: function(msg){ this.msg = msg; } } } </script> <style scoped> </style>
Vuex
是一個專爲Vue.js
應用程序開發的狀態管理模式,其採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
每個Vuex
應用的核心就是store
倉庫,store
基本上就是一個容器,它包含着你的應用中大部分的狀態state
。Vuex
和單純的全局對象有如下兩點不一樣:
Vuex
的狀態存儲是響應式的,當Vue
組件從store
中讀取狀態的時候,若store
中的狀態發生變化,那麼相應的組件也會相應地獲得高效更新。store
中的狀態,改變store
中的狀態的惟一途徑就是顯式地提交mutation
,這樣使得咱們能夠方便地跟蹤每個狀態的變化。實際上咱們能夠獲得更多使用Vuex
的優勢:
Vuex
專作態管理,由一個統一的方法去修改數據,所有的修改都是能夠追溯的。Vuex
更方便。Vuex
不會形成全局變量的污染,同時解決了父組件與孫組件,以及兄弟組件之間通訊的問題。固然若是項目足夠小,使用Vuex
多是繁瑣冗餘的。若是應用夠簡單,最好不要使用Vuex
,上文中的一個簡單的store
模式就足夠了。
在下面例子中,咱們經過提交mutation
的方式,而非直接改變store.state.count
,是由於咱們想要更明確地追蹤到狀態的變化。這個簡單的約定可以讓你的意圖更加明顯,這樣你在閱讀代碼的時候能更容易地解讀應用內部的狀態改變。此外這樣也讓咱們有機會去實現一些能記錄每次狀態改變,保存狀態快照的調試工具。
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment: function(state) { state.count++; } } }) store.commit("increment"); console.log(store.state.count); // 1
因爲store
中的狀態是響應式的,在組件中調用store
中的狀態簡單到僅須要在計算屬性中返回便可。觸發變化也僅僅是在組件的methods
中提交mutation
便可。
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment: state => state.count++, decrement: state => state.count-- } }) new Vue({ el: "#app", store, computed: { count: function() { return this.$store.state.count; } }, methods: { increment: function() { this.$store.commit("increment"); }, decrement: function() { this.$store.commit("decrement"); } } })
https://github.com/WindrunnerMax/EveryDay
https://zhuanlan.zhihu.com/p/109700915 https://juejin.cn/post/6844903887162310669 https://juejin.cn/post/6844903784963899405 https://segmentfault.com/a/1190000022083517 https://github.com/yangtao2o/learn/issues/97 https://github.com/YangYmimi/read-vue/issues/12