@淺談Vue組件通訊javascript
create by db on 2019-8-15 19:41:55
Recently revised in 2019-9-25 17:11:00php
Hello 小夥伴們,若是以爲本文還不錯,麻煩點個贊或者給個 star,大家的贊和 star 是我前進的動力!GitHub 地址css
這是一篇欠下好久的文章了。對於以Vue爲工做棧的我來講,Vue組件之間的通訊問題是接觸最多的問題之一。所以,參考一些文章,並結合本身的工做及學習經驗,寫下這篇博客——溫故而知新。 做爲一隻前端菜鳥,本篇文章旨在記錄本身的學習心得,若有不足,還請多多指教,謝謝你們。html
I hear and I fogorget.前端
I see and I remember.vue
I do and I understand.java
組件間的通訊是是實際開發中很是經常使用的一環,Vue中實現組件之間的通訊方式有不少種, props,eventBus, Vuex, v-on, ref...等等。如何使用組件通訊,對項目總體設計、開發、規範都有很實際的的做用。Vue文檔上以及各種大佬博客中對總結vue組件間通訊都寫的很詳細了,我也拋磚引玉,淺談一下vue組件間通訊的幾種方式以及各自的使用場景。git
參考文獻:github
Vue組件通訊包括:子組件與父組件之間,兄弟組件之間,模塊之間vuex
一言不合上代碼:
index.vue
父組件
<template>
<div>
<h1>我是父元素</h1>
<child title="靜態文字" :img-width="344" :img-height="imgHeight" :before-close="closeFuction" @dadEmit='sonEmit'></child>
</div>
</template>
<script> import child from './child.vue' // 引入子組件 export default { data () { return { imgHeight: 300, } }, components: { child // 聲明子組件 }, methods: { closeFuction () { console.log('叫爸爸!') }, sonEmit (msg) { console.log('兒子你說啥?') console.log(msg) } } } </script>
複製代碼
child.vue
子組件
<template>
<div class="hello">
<h1>我是子元素</h1>
<h1>{{ title }}</h1>
<button @click="clickEmit">點我給父元素傳值</button>
</div>
</template>
<script> export default { name: 'child', // 接收父子組件參數 props: { imgWidth: { type: Number, // 數據類型 default: 300 // 默認值 }, imgHeight: { type: Number }, title: { type: String, default: '' }, beforeClose: { type: Function, default: function () { console.log('你閉嘴'); } } }, created () { console.log(this.imgWidth) console.log(this.imgHeight) this.beforeClose() }, methods: { // $emit給父組件傳值 clickEmit () { this.$emit('dadEmit', "你這個糟老頭太壞了!") } } } </script>
<style scoped> .hello { background: yellow; } </style>
複製代碼
父組件傳遞數據時相似在標籤中寫了一個屬性,若是是傳遞的數據是data
中的天然是要在傳遞屬性前加:
(v-bind
的縮寫),若是傳遞的是一個已知的固定值呢
字符串
是靜態的可直接傳入無需在屬性前加:
數字
,布爾
,對象
,數組
,由於這些是js表達式而不是字符串,因此即便這些傳遞的是靜態的,也須要前面加上:
綁定,把數據放到data
中引用,若是prop
傳到子組件中的數據是一個對象
的話,要注意傳遞的是一個對象引用
,雖然父子組件看似是分離的但最後都是在同一對象下。
prop
傳到子組件的值只是做爲初始值使用,且在父組件中不會變化賦值到data
中使用prop
的數據在父組件會被改變的,放到計算屬性中監聽變化使用。由於若是傳遞的是個對象
的話,只改變下面的某個屬性子組件中是不會響應式更新的,若是子組件須要在數據變化時響應式更新那隻能放到computed
中或者用watch
深拷貝deep:true
才能監聽到變化prop
傳遞數據的變化作些操做,那麼寫在computed
中會報警告,由於計算屬性中不推薦有任何數據的改變,最好只進行計算。若是你非要進行數據的操做那麼能夠把監聽寫在watch
(注意deep深拷貝)或者使用computed
的get
和set
。 但問題又來了,若是你傳進來的是個對象
,同時你又須要在子組件中操做傳進來的這個數據,那麼在父組件中的這個數據也會改變,由於你傳遞的只是個引用, 即便你把prop
的數據複製到data
中也是同樣的,不管如何賦值都是引用的賦值,你只能對對象作深拷貝建立一個副本才能繼續操做,你能夠用JSON的方法先轉化字符串在轉成對象更方便一點
JSON.stringify(obj)
將JSON對象轉爲字符串。JSON.parse(string)
將字符串轉爲JSON對象格式。因此在父子傳遞數據時要先考慮好數據要如何使用,不然你會遇到不少問題或子組件中修改了父組件中的數據,這是很隱蔽而且很危險的。
m.$emit( eventName, […args] )
參數:
{string} eventName // 父元素中定義的事件名稱
[...args] // 參數
// 觸發當前實例上的事件。附加參數都會傳給監聽器回調。
複製代碼
以上是Vue官網給$emit
的定義。
簡而言之,能夠經過監聽當前實例上的自定義事件,經過vm.$emit
觸發父元素上定義的事件,將[...args]中的參數傳給父元素
兄弟組件通訊有兩種方法,eventBus
,Vuex
。
eventBus
的原理是引入一個新的vue
實例,而後經過分別調用這個實例的事件觸發和監聽來實現通訊和參數傳遞。
eventBus.js
文件 —— 咱們通常會直接用一個公共文件來存放vue實例
import Vue from 'vue';
export default new Vue();
複製代碼
咱們在apple.vue
中監聽一個事件
apple.vue
文件
<template>
<div class="hello">
<h1>我是蘋果</h1>
<h1>{{ title }}</h1>
</div>
</template>
<script> import eventBus from './eventBus.js' export default { name: 'orange', data () { return { title: 300 } }, // 咱們在created鉤子中監聽方法 created () { // 在created()鉤子中調用eventBus監聽getTarget事件,並接受參數,綁定方法 eventBus.$on('getTarget', this.getTarget) // eventBus.$on('getTarget', target => { // 也能夠在後面直接寫方法 // this.title = target // }) }, beforeDestroy () { // 組件銷燬前須要解綁事件。不然會出現重複觸發事件的問題 eventBus.$off('getTarget', this.getTarget) }, methods: { getTarget (param) { this.title = param } } } </script>
<style scoped> .hello { background: red; } </style>
複製代碼
咱們在orange.vue
中觸發eventBus
orange.vue
文件
<template>
<div class="hello">
<h1>我是橙子</h1>
<h1>{{ title }}</h1>
<button @click="doSomething">點擊觸發eventBus</button>
</div>
</template>
<script> import eventBus from './eventBus.js' export default { name: 'orange', data () { return { title: 300 } }, methods: { // $emit觸發事件getTarget事件 doSomething () { // 向getTarget方法傳參22 eventBus.$emit('getTarget', 22) } } } </script>
<style scoped> .hello { background: orange; } </style>
複製代碼
eventBus
其實很是方便,任何的組件通訊都能用它來完成。可是,咱們會根據狀況來選擇更易維護的方式。由於eventBus
比較很差找到對應的監聽或者觸發事件具體實現的地方,因此通常組件通訊更考慮Vuex
的實現方式。
在模塊之間通訊利用eventBus
,而後在模塊內部,利用Vuex
通訊,維護數據,會在邏輯上比較清晰。
當非父子組件之間通訊較多時,用eventBus
很容易邏輯混亂,較難維護。Vuex
將狀態管理單獨拎出來,應用統一的方式進行處理,能夠理解爲組件間公用的一個全局對象。
安裝vuex,使用命令:
npm install vuex --save
store/index.js
import Vuex from 'vuex' // 引入Vuex
import Vue from 'vue' // 引入Vue
// 使用Vuex
Vue.use(Vuex)
// 建立Vuex實例
const store = new Vuex.Store({
// state:vuex中的數據源,咱們須要保存的數據就保存在這裏,能夠在頁面經過 this.$store.state.stateName來獲取咱們定義的數據;
state: {
stateName: '哈哈哈'
},
// mutations:修改store中的值惟一的方法就是提交mutation,能夠在組件中使用 this.$store.commit('xxx') 提交 mutation
mutations: {
mutationsName (state, params) { // 定義更改state的方法,能夠傳參,必須是同步函數
state.stateName = params
}
},
// Action 提交的是 mutation,而不是直接變動狀態。Action 能夠包含任意異步操做。相似於vue的methods。能夠在組件中使用this.$store.dispatch('actionName', 'xxx')分發
actions: {
actionName (contest, params) { // 觸發mutation 方法要用commit分發,以此改變state
contest.commit('mutationsName', params)
}
},
// getters:至關於Vue中的computed,能夠用於監聽、state中的值的變化,返回計算後的結果。能夠在組件中使用this.$store.getters.getStateName獲取其中的值
getters: {
getStateName: state => {
return state.stateName
}
}
})
export default store // 導出store
複製代碼
main.js
文件
// store爲實例化生成的
import store from './store/index.js';
new Vue({
el: '#app',
store, // 將store掛載到vue實例上
render: h => h(App)
})
複製代碼
若是咱們不喜歡這種在頁面上使用
this.$store.state.stateName
this.$store.getters.getStateName
this.$store.dispatch('actionName', 'xxx')
這種很長的寫法,那麼咱們可使用mapState
、mapGetters
、mapActions
就不會這麼麻煩了;
child.vue
文件
<template>
<div class="hello">
<h1>我是香蕉</h1>
mapState取值:<h2>{{ stateName }}</h2>
mapGetters取值:<h2>{{ getStateName }}</h2>
<button @click="clickAction">使用action改值</button>
<button @click="clickMutation">使用mutation改值</button>
<button @click="clickCommit">使用commit改值</button>
<button @click="clickDispatch">使用dispatch改值</button>
</div>
</template>
<script> import { mapActions, mapMutations, mapState, mapGetters } from 'vuex' export default { name: 'banana', computed: { ...mapGetters(['getStateName']), ...mapState(['stateName']) }, methods: { // 使用輔助函數直接將觸發函數映射到methods上 ...mapActions(['actionName']), ...mapMutations(['mutationsName']), // 點擊觸發 clickAction () { this.actionName('使用action改值') }, clickMutation () { this.mutationsName('使用mutation改值') }, clickCommit () { this.$store.commit('mutationsName', '使用commit改值') }, clickDispatch () { this.$store.dispatch('actionName', '使用dispatch改值') } } } </script>
<style scoped> .hello { background: yellow; } </style>
複製代碼
當兄弟組件不少,涉及到的處理數據龐大的時候,能夠用到vuex中的modules,使得結構更加清晰
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
複製代碼
vuex講細篇幅很長,更多更復雜的內容,參考官方教程
路漫漫其修遠兮,與諸君共勉。
後記:Hello 小夥伴們,若是以爲本文還不錯,記得點個贊或者給個 star,大家的贊和 star 是我編寫更多更豐富文章的動力!GitHub 地址
db 的文檔庫 由 http://www.javashuo.com/tag/db 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議進行許可。
基於github.com/danygitgit上的做品創做。
本許可協議受權以外的使用權限能夠從 creativecommons.org/licenses/by… 處得到。