天地不仁以萬物爲芻狗,宇宙無義視衆生如螻蟻
——蕭鼎和我html
上一節列出了5個關鍵點,第一個路由已經解決了,接下來解決第二個問題:vue
組件的通訊問題web
1、組件的關係vue-router
組件之間的關係無非就是兩種父子關係和沒有父子關係。爲何我這樣說呢?
按道理應該還有兄弟關係(也就是並列的組件,好比一個組件中引用了hreder和footer組件。),還有爺孫關係(好比我有七個Calabash Brothers組件,放在的HanHan組件下,而HanHan組件放在了Chairman Mao組件下)
那麼不該該是父子、爺孫、兄弟關係嗎?
然而並非,由於我看了vue的文檔。他的意思就是父子通訊和非父子通訊。vuex
2、父子之間的通訊——Prop和自定義事件npm
組件實例的做用域是孤立的。這意味着不能而且不該該在子組件的模板內直接引用父組件的數據。
prop 是父組件用來傳遞數據的一個自定義屬性。子組件須要顯式地用 props 選項聲明 「prop」。
將咱們的App.vue看成父組件,將test看成子組件(什麼看成,原本就是)。
在App.vue中修改咱們的<test>:segmentfault
<!-- 傳遞一個say值 --> <test say="你是豬"></test>
在Test.vue中接收,並在頁面中顯示:瀏覽器
<template> <div> <p>我是全英雄聯盟最騷的騷豬</p> <p>說: {{say}}</p> <!-- 綁定say值到頁面上 --> </div> </template> <script> export default { name: 'test', props: ['say'] //這裏接收父組件穿過來的say值 } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> p { color: red; } </style>
而後在瀏覽器的顯示效果以下:
bash
綜上所述能夠看出,其實所謂的prop就是在<test></test>標籤添加一個自定義的屬性,而後在子組件中取出這個屬性,用Jquery也能夠作嘛(滿腦子,騷,騷想法.jpg)。app
上面的例子很漂亮的把父傳子的通訊方式展示出來了。可是子傳父呢?
vue文檔中使用的自定義事件。
使用 $on(eventName) 監聽事件
使用 $emit(eventName) 觸發事件
咱們仍是用APP.vue做爲父組件,Test.vue做爲子組件
App.vue
... /*增長一個位置來顯示子組件傳過來的值*/ <p>我兒子對我說: {{noSay}}</p> /* 增長一個自定義的事件mychild,並給他指定觸發的方法*/ <test say="你是豬" v-on:myChild="toFatherSay"></test> ... data () { return { noSay: '' // 用來接收子組件穿過來的數據 } }, methods: { toFatherSay: function(massage) { // mychlid事件觸發調用的方法 this.noSay = massage // massage就是子組件穿過來的內容 } }
Test.vue
.... /*增長一個按鈕,一點擊就向父組件傳值*/ <button v-on:click="toFather">點我傳給父組件</button> .... data() { return { massage: '我纔不說呢' // 定義一個向父組件傳遞的值 } }, methods: { toFather: function() { // 按鈕點擊觸發的方法 this.$emit('myChild',this.massage)// 使用$emit來向父組件傳播 } }, ....
作完以上操做以後在瀏覽器上測試:
3、非父子關係之間的通訊——eventBus
在veu文檔上,非父子之間的通訊是經過使用一個空的Vue實例做爲中央事件總線。
空的Vue實例? and 中央事件總線?
空的Vue實例也就是說
var bus = new Vue(); // 的確是一個空的
中央事件總線,難道組件通訊要經過全局的事件來進行?
的確是這樣,vue提供了$emit和$on方法來進行參數監聽(其實就是個發佈訂閱模式)。
建立一個空的Vue實例 Bus.js:
import Vue from 'vue' export default new Vue();
將咱們的Apart.vue和Bpart.vue看成非父子關係組件:
Apart.vue
<template> <div> <p>我是Apart</p> <a v-on:click="goPage">點我切換</a> </div> </template> <script> import Bus from '../Bus' //引入建立的Bus對象 export default { name: 'test', methods: { goPage: function () { Bus.$emit('whiteSay', '克里斯,關下門') // 使用$emit方法建立一個鍵值對 this.$router.push('/bb') } }, /*將引入的Bus組件注入咱們的組件對象中 import進來是不夠的 還要讓組件對象知道 */ components: { Bus } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> p { color: red; } div { width: 100%; height: 100px; background-color: green; } </style>
Bpart.vue
<template> <div> <p>我是Bpart</p> <p>Apart對我說: {{whiteSay}}</p> <a v-on:click="goPage">點我切換</a> </div> </template> <script> import Bus from '../Bus' // 一樣要引入Bus export default { name: 'test', data () { // data用於存放組件的數據,必須是一個function,而且返回一個對象 return { whiteSay: 'nihao' } }, methods: { goPage: function () { this.$router.push('/') } }, components: { // 一樣要注入Bus Bus }, created: function() { // 在組件被建立時候將會執行此函數 至關於進入頁面的自執行 Bus.$on('whiteSay', function(data) { // 使用$on方法監聽white屬性並執行一個回調函數 this.whiteSay = data console.log(this.whiteSay) }); } } </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> p { color: red; } div { width: 100%; height: 100px; background-color: yellow; } </style>
而後在瀏覽器中測試一下:
有問題!!!不管怎麼點擊咱們發現Bpart中定義的whiteSay並無改變,而且第一次點擊控制檯沒有打印。我在Bpart中寫了這段代碼:
data () { return { whiteSay: 'nihao' } }, created: function() { // 在組件被建立時候將會執行此函數 至關於進入頁面的自執行 Bus.$on('whiteSay', function(data) { // 使用$on方法監聽white屬性並執行一個回調函數 this.whiteSay = data console.log(this.whiteSay) }); }
按道理在元素被建立的時候,會將監聽到的值賦給whiteSay而且打印。可是咱們注意到第一次點擊,兩個操做都沒有執行,也就是說沒有監聽到whiteSay值的變化。而第二次以後都監聽到了。這是爲何?爲何把值賦給data中定義的whiteSay以後沒有網頁沒有更新?
帶着這兩個問題我去問了度娘和股哥。一下是答案:
第一個爲何: 項目中使用了vue-router,會先加載新的組件,等新的組件渲染好可是還沒掛載前,銷毀舊組件,在掛載新組件。將Apart.vue的代碼修改成:
... methods: { goPage: function () { this.$router.push('/bb') } }, /*Vue 實例銷燬後調用 就是所謂的生命週期鉤子*/ destroyed() { Bus.$emit('whiteSay', '克里斯,關下門') // 使用$emit方法建立一個鍵值對 }, ...
這樣第一個問題就解決了。附上找到的答案鏈接:https://segmentfault.com/q/10...
第二個爲何:這個是我本身代碼有問題,問了隔壁大神。說是個人做用域有問題,將Bpart.vue中的代碼改成:
··· created: function() { // 在組件被建立時候將會執行此函數 至關於進入頁面的自執行 var _self = this; // 將當前做用域保存在變量中,和$on()的做用域區分開來 Bus.$on('whiteSay', function(data) { // 使用$on方法監聽white屬性並執行一個回調函數 _self.whiteSay = data console.log(_self.whiteSay) }); } ···
這樣全部的問題就都解決了。
4、Vuex
當我使用了上面幾種方法來實現組件的通訊存在着一些缺陷。好比父組件向子組件傳一個值,子組件將值處理完了返回給父組件,這將同時用到prop和自定義事件。還不如直接寫一個全部組件均可以訪問的變量呢來得方便呢。好比:
/*這是vuex文檔中的例子*/ const sourceOfTruth = {} const vmA = new Vue({ data: sourceOfTruth }) const vmB = new Vue({ data: sourceOfTruth })
再好比當項目過大,組件之間的通訊將變得難以管理。veux的初衷就是爲什麼更好的管理組件的狀態。一下是vuex文檔對vuex的定義:
Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式。它採用集中式存儲管理應用的全部組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。Vuex 也集成到 Vue 的官方調試工具 devtools extension,提供了諸如零配置的 time-travel 調試、狀態快照導入導出等高級調試功能。
寫得好累,還好最近沒事作,不會被boss罵。
接下來直接開始使用vuex。
先下載
在根目錄下打開cmd:
npm install vuex -save
下載成功看到一下數據:
C:\Users\59227\Desktop\x-chat>npm install vuex --save x-chat@1.0.0 C:\Users\59227\Desktop\x-chat `-- vuex@2.1.1 npm WARN optional Skipping failed optional dependency /chokidar/fsevents: npm WARN notsup Not compatible with your operating system or architecture: fsevents@1.0.15
而後在main.js中引用,並安裝到Vue上面
import Vuex from 'vuex' Vue.use(Vuex)
前面兩步將Vuex引入到了項目當中,接下來如何使用Vuex。
Vuex的核心是一個store(倉庫)這個倉庫的做用就是用來管理應用中的state(狀態)。這裏狀態該怎麼理解?
我我的的理解是:全部組件共享的並能夠進行更改的對象。
除了state的,store還有getter、Mutations、Actions以及Modules。在vuex文檔中都有很是詳細的說明:http://vuex.vuejs.org/zh-cn/s...
籠統的說:
組件獲取 state 用 vuex 的 getter
組件觸發動做用 vuex 的 action
修改 state 用 vuex 的 mutation
知乎上看到的,說得很貼切易懂。
直接上代碼,建議擼完代碼,再去看一遍vuex的文檔。
main.js
.... const store = new Vuex.Store({ //建立一個倉庫 state: { showDagger: true, // 定義一個狀態 }, mutations: {// 定義 mutation ,更改 Vuex 的 store 中的狀態的惟一方法是提交mutation daggerCtrl (state) { // 必定要傳入state,而且是第一個參數 state.showDagger = !state.showDagger // 將showDagger值取反 } } }) /* eslint-disable no-new */ new Vue({ el: '#app', router, // 將router對象傳給vue,這樣就能夠經過this.$router獲取到router對象了 store, // 將store對象傳給vue,這樣就能夠經過this.$store獲取到store對象了 template: '<App/>', components: { App } })
而後更改App.vue:
<template> <div id="app"> <img src="./assets/logo.png"> <p>我兒子對我說: {{noSay}}</p> <test say="你是豬" v-on:myChild="toFatherSay"></test> <router-view></router-view> <!-- @click是v-on:click的簡寫方式 --> <button @click="changeDagger">dagger</button> 1.添加按鈕和組件 <dagger></dagger> </div> </template> <script> import Test from './components/Test' // 這裏引入Test組件 import Dagger from './components/Dagger' // 引入Dagger組件 2.引入Dagger export default { name: 'app', components: { Test, // 在components中添加Test Dagger 3.注入Dagger }, data () { return { noSay: '' } }, methods: { toFatherSay: function(massage) { this.noSay = massage }, changeDagger: function() { 4.增長按鈕點擊觸發的事件 this.$store.commit('daggerCtrl') // 使用commit(提交)方法喚醒名爲daggerCtrl的mutation } } } </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: 60px; } </style>
dagger.vue
<template> <div class="dagger" v-if="this.$store.state.showDagger"> <h1>Dagger</h1> </div> </template> <script> </script> <style scoped> .dagger { margin: 0 auto; width: 50%; height: 100px; background-color: red; } </style>
打開瀏覽器 看效果:
使用vuex實現組件通訊就搞定了,更多的用法請參考vuex文檔。