前幾話說到用vuejs的路由實現一個複雜的單頁應用,實現了幾個小功能。
這一話來總結組件間如何通信。
一、父組件向子組件傳消息
項目目錄結構
1、子組件
子組件定義props
src/components/ParentToChild/ChildPage.vue
爲了區分,這個頁面是藍色的
msgFromParent是從父組件傳來的值
<template>
<div class="childBack">
<h3>我是子組件childPage</h3>
<div>{{msgFromParent}}</div>
</div>
</template>
<script>
export default {
name: "",
props:['msgFromParent']
}
</script>
<style scoped>
.childBack{
background-color: #5ed5ed;
}
</style>
2、父組件
父組件引入子組件頁面ChildPage.vue
src/components/ParentToChild/ParentPage.vue
父組件頁面是粉色的~
msgFromParent定義了給子組件的值
引入就是
(1)import一下
(2)然後在components裏聲明一下,起個別名
(3)再在template標籤裏用剛纔起的別名加進來就好了
<template>
<div class="parentBack">
<h3>我是父組件ParentPage</h3>
<childPage msgFromParent="我是父組件給子組件的消息"></childPage>
</div>
</template>
<script>
import ChildPage from './ChildPage'
export default {
name: "",
components: {
"childPage" : ChildPage
}
}
</script>
<style scoped>
.parentBack{
background-color: #ffb7b3;
}
</style>
3、App.vue
App.vue引入父組件ParentPage.vue
<template>
<div id="app">
<!--<img src="./assets/logo.png">-->
<router-view/>
<parentPage></parentPage>
</div>
</template>
<script>
import ParentPage from './components/ParentToChild/ParentPage'
export default {
name: 'App',
components: {
"parentPage": ParentPage
}
}
</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>
其實這裏就是App.vue是ParentPage.vue的父組件,ParentPage.vue就是ChildPage.vue的父組件
效果:
看吧,子組件頁面裏,顯示了來自父組件的消息。好簡單喲~
父組件還可以這樣寫
<template>
<div class="parentBack">
<h3>我是父組件ParentPage</h3>
<childPage v-bind:msgFromParent="sendMsgToChild"></childPage>
</div>
</template>
<script>
import ChildPage from './ChildPage'
export default {
name: "",
components: {
"childPage" : ChildPage
},
data() {
return {
sendMsgToChild : "我也是父組件給子組件的消息"
}
}
}
</script>
<style scoped>
.parentBack{
background-color: #ffb7b3;
}
</style>
效果一樣~
二、子組件向父組件傳值
項目目錄
1、子組件
src/components/ChildToParent/CToP_childPage.vue
這裏有個很醜的默認按鈕,點擊就會向父組件傳值,通過$emit
第一個參數是事件的名稱,父組件裏面監聽這個事件
第二個參數就是消息的內容。
<template>
<div class="childBack">
<h4>我是子組件CToP_childPage(我是藍色的)</h4>
<button @click="sendMsgToParent">向父組件傳值</button>
</div>
</template>
<script>
export default {
name: "",
methods:{
sendMsgToParent: function () {
this.$emit("msgFromChildToParent", "Hello, parent, I am msg from child.");
}
}
}
</script>
<style scoped>
.childBack{
background-color: #5ed5ed;
}
</style>
2、父組件
父組件要引入子組件
父組件在引入子組件的標籤裏,綁定msgFromChildToParent這個事件
這裏receiveChildMsg方法,收到的msg就是子組件傳過來的值。
<template>
<div class="parentBack">
<h3>我是父組件CToP_ParentPage(我是粉色的)</h3>
<ctoP_childPage v-on:msgFromChildToParent="receiveChildMsg"></ctoP_childPage>
<div>{{msgFromChild}}</div>
</div>
</template>
<script>
import ctoP_childPage from './CtoP_ChildPage'
export default {
name: "",
components: {
"ctoP_childPage" : ctoP_childPage
},
data() {
return {
msgFromChild: ""
}
},
methods:{
receiveChildMsg:function (msg) {
this.msgFromChild = msg;
}
}
}
</script>
<style scoped>
.parentBack{
background-color: #ffb7b3;
}
</style>
3、App.vue引入父組件
<template>
<div id="app">
<!--<img src="./assets/logo.png">-->
<router-view/>
<div>
<h3>父組件向子組件傳消息Demo</h3>
<parentPage></parentPage>
</div>
<div>
<h3>子組件向父組件傳消息Demo</h3>
<ctoP_ParentPage></ctoP_ParentPage>
</div>
</div>
</template>
<script>
import ParentPage from './components/ParentToChild/ParentPage'
import CtoP_ParentPage from './components/ChildToParent/CtoP_ParentPage'
export default {
name: 'App',
components: {
"parentPage": ParentPage,
"ctoP_ParentPage": CtoP_ParentPage
}
}
</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>
看下效果
紅框裏就是剛寫的例子
點擊這個很醜的、沒有寫樣式的默認按鈕
這裏紅框中多了一行字,上面爲了區分把父組件定義成粉色的。
父組件顯示了來自子組件的值。
如果覺得繞暈了,上面兩個例子自己敲一遍就能領悟了。
這裏補充一下v-on和v-bind的區別
兩個demo中分別用了v-bind和v-on來綁定
第一個父傳子中
<childPage v-bind:msgFromParent="sendMsgToChild"></childPage>
第二個子傳父中
<ctoP_childPage v-on:msgFromChildToParent="receiveChildMsg"></ctoP_childPage>
通過細心的比較發現
v-bind綁定的是一個字段,一個屬性,雙引號裏面的sendMsgToChild是要在data裏面定義的,是一個值;
v-on綁定的是一個事件,雙引號裏面的receiveChildMsg是在methods裏面定義的,是一個方法。
以上就是父子組件之間的通信了。
但是上述的是單向數據流,而且只適用在有父子組件關係的組件之間。
在我前幾話寫的單頁路由vuejs項目中,有些組件根本就沒有父子關係,但是要共享同一個數據源,而且每個頁面(組件)都會頻繁的去改動同一個數據源的某個值。
有一種古老的做法是,數據放在localstorage裏面,這樣的話也不是不可以做,但是在一個多人合作的項目裏,這樣就非常難管理。
下面就介紹vuex通信。
起初也覺得vuex看起來摸不着頭腦,看網上也說學習成本有點高,在仔細閱讀文檔並動手實踐以後,發現還是在我的智商可以學習的範圍內的。
三、vuex
vuex我用之前前幾話寫的那個路由的例子來舉例,篇尾會附上代碼鏈接。
使用的場景就是:
複雜單頁應用,各路由頁面之間(不是父子組件關係)共用一個數據源,比如用戶信息,一些詳情呀,然後這個數據源裏面的部分數據,在各個頁面都可能會被修改的,修改以後要每個頁面顯示都相應變化。
vuex官方說明鏈接
這裏有說到,vuex是狀態管理的意思嘛,我在項目裏不只是存儲了一個計數變量,而是一個json對象的字符串,大概幾百byte吧,也沒問題。如果只是一個兩個值需要計數,我想我可能不會引入vuex來做了。
就先說怎麼用和效果吧。
安裝
cnpm install vuex --save
或者
npm install vuex --save
新建一個目錄vuex,裏面新建五個js文件
第一個src/vuex/state.js,這個就是放數據的地方。
/**
*@Author:Yolanda
*@Date: 2020/5/28 12:05
*/
const state = {
shareData: {
name: "Yolanda",
age: 18,
city: "Shanghai",
hobby: "Yoga"
}
};
export default state;
第二個文件src/vuex/mutations.js
/**
*@Author:Yolanda
*@Date: 2020/5/28 12:05
*/
const mutations = {
update(state, shareData){
state.shareData = shareData;
}
};
export default mutations;
這個地方就是放同步修改數據的函數,這裏只寫了一個函數update,第一個參數是state,第二是寫在state.js裏面的json對象shareData.
第三個文件src/vuex/actions.js和src/vuex/getters.js,先建了空着吧,暫時不用它們,一會再寫。
第五個文件src/vuex/store_index.js
/**
*@Author:Yolanda
*@Date: 2020/5/28 12:06
*/
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
actions,
getters
});
到這一步離成功已經非常接近了。
接下來在main.js引入vuex。
找一個地方import一下
// 引入
import store from './vuex/store_index'
然後原來這個地方加個store
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
整個main.js加了之前的好多東西,但是貼一下參考吧
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
// import router from './router'
import VueRouter from 'vue-router'
import VueResource from 'vue-resource'
import FirstPage from './components/First/FirstPage'
import SecondPage from './components/SecondPage'
import ThirdPage from './components/ThirdPage'
import FirstPageBPart from './components/First/FirstPageBPart'
// 引入bootstrap
import './assets/bootstrap-3.3.7-dist/js/bootstrap'
import './assets/bootstrap-3.3.7-dist/css/bootstrap.css'
Vue.config.productionTip = false
Vue.use(VueRouter);
Vue.use(VueResource);
import $ from 'jquery'
// 引入按鍵指令
import './directives/keyDirective'
import './directives/keyMapping'
// 確認取消對話框
import messageBox from './components/MessageBox/messageBox'
Vue.use(messageBox);
import './assets/css/bootstrap-pretty.css'
// 引入
import store from './vuex/store_index'
// 定義路由
let router = new VueRouter({
linkActiveClass: 'active',
routes: [
{
path: '/',
redirect: 'FirstPageLink'
},
{
path: '/FirstPageLink',
components: {
default: FirstPage,
FirstPageBPartLink : FirstPageBPart
}
},
{
path: '/SecondPageLink',
component: SecondPage
},
{
path: '/ThirdPageLink',
component: ThirdPage
}
]
})
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
好啦,寫完了定義全局數據和update方法,就可以來看效果啦。其實還挺簡單的是不啦?
隨便找個.vue文件,我就放在我寫的src/components/SecondPage.vue和src/components/ThirdPage.vue啦
<div>
<h4>通過vuex拿到的全局數據:{{$store.state.shareData.city}}</h4>
</div>
現在第二個頁面和第三個頁面都拿到state數據裏面的城市值了
接下來再在src/components/SecondPage.vue寫個方法去修改它
<div>
<button class="btn btn-brown" @click="updateShareData">修改vuex數據</button>
</div>
修改的方法這樣寫
updateShareData() {
let shareData = {
name: "Annie",
age: 16,
city: "Beijing",
hobby: "Dance"
}
this.$store.commit("update", shareData);
}
整個的src/components/SecondPage.vue文件代碼貼一下,供參考。
<template>
<div v-key-bind-listen>
{{msg}}
<div>
<button class="btn btn-info" @click="openDialog">打開確認取消框</button>
</div>
<div>
<h4>通過vuex拿到的全局數據:{{$store.state.shareData.city}}</h4>
</div>
<div>
<button class="btn btn-brown" @click="updateShareData">修改vuex數據</button>
</div>
</div>
</template>
<style>
body {
/*background-color: green;*/
}
</style>
<script>
export default {
data() {
return {msg: '大家好!我是第二個頁面'}
},
methods: {
openDialog(){
this.$messageBox.showMsgBox({
title: '提示',
content: '這是一個確認取消框'
}).then(async (val) => {
// 這裏寫確認按鈕的方法
console.log("你按了確定")
}).catch(() => {
// 這裏寫取消按鈕的方法
console.log("你按了取消")
});
},
updateShareData() {
let shareData = {
name: "Annie",
age: 16,
city: "Beijing",
hobby: "Dance"
}
this.$store.commit("update", shareData);
}
}
}
</script>
好啦看下效果
沒點擊按鈕修改值之前
可以看到第二個第三個頁面的全局數據都是上海,Shanghai
點擊按鈕修改
可以看的第二個第三個的全局數據都變成了北京,Beijing。
從現在開始,就有一個全局的數據啦,在一處修改,其他地方都同步生效的那種。
再來寫一下,官網手冊裏介紹的使用常量替代Mutation事件類型吧~
這個是啥意思呢?其實就是mutations.js那個裏面的函數名,用常量來表示。
爲啥要用這個呢?就是用了以後,感覺就有種很規範的感覺。不用完全沒得問題。
我在項目裏用了。
還是在vuex目錄裏新建一個mutation-types.js文件
裏面就是定義一個常量等於函數的名字
/**
*@Author:Yolanda@
*@Date: 2020/6/1 18:38
*/
export const USER_UPDATE = 'update';
然後src/vuex/mutations.js要修改一下,給人家的函數起了別名,肯定要用上的嘛
用哪個文件就引入哪個文件。
/**
*@Author:Yolanda
*@Date: 2020/5/28 12:05
*/
// 引入常量函數名
import * as mutationTypes from './mutation-types'
const mutations = {
[mutationTypes.USER_UPDATE](state, shareData){
state.shareData = shareData;
}
};
export default mutations;
然後剛纔SecondPage.vue裏面的這句話
this.$store.commit("update", shareData);
就要改成這個樣子了
// mutations函數不用常量名
// this.$store.commit("update", shareData);
// mutations函數用常量名
this.$store.commit(mutationTypes.USER_UPDATE, shareData);
在SecondPage.vue裏面也要引入哦
// 引入常量函數名
import * as mutationTypes from '../vuex/mutation-types'
然後試一下,效果是一樣的。
如果你是多人一起開發,有好多個函數,就可以這樣用常量名來代替函數名了,還是方便的。
如果你只有一兩個,這樣寫就有點雞肋了。
mapMutations
再說一下mapMutations,這種帶map…的其實就是簡化一下調用
原先咱們的那個src/vuex/mutations.js最開始這樣寫的嘛
/**
*@Author:Yolanda
*@Date: 2020/5/28 12:05
*/
// 引入常量函數名
import * as mutationTypes from './mutation-types'
const mutations = {
update(state, shareData){
state.shareData = shareData;
},
// 常量函數名
// USER_UPDATE(state, shareData){
// state.shareData = shareData;
// }
};
export default mutations;
在SecondPage.vue用的時候,在methods裏面,重命名一下
methods: {
...mapMutations({
update: 'update'
})
}
注意這裏要引入
import { mapMutations } from 'vuex'
哎呀,其實很簡單啦,貼一下vue文件全部代碼
<template>
<div v-key-bind-listen>
{{msg}}
<div>
<button class="btn btn-info" @click="openDialog">打開確認取消框</button>
</div>
<div>
<h4>通過vuex拿到的全局數據:{{$store.state.shareData.city}}</h4>
</div>
<div>
<button class="btn btn-brown" @click="updateShareData">修改vuex數據</button>
</div>
</div>
</template>
<style>
body {
/*background-color: green;*/
}
</style>
<script>
// 引入常量函數名
import * as mutationTypes from '../vuex/mutation-types'
import { mapMutations } from 'vuex'
export default {
data() {
return {msg: '大家好!我是第二個頁面'}
},
methods: {
...mapMutations({
update: 'update'
}),
openDialog(){
this.$messageBox.showMsgBox({
title: '提示',
content: '這是一個確認取消框'
}).then(async (val) => {
// 這裏寫確認按鈕的方法
console.log("你按了確定")
}).catch(() => {
// 這裏寫取消按鈕的方法
console.log("你按了取消")
});
},
updateShareData() {
let shareData = {
name: "Annie",
age: 16,
city: "Beijing",
hobby: "Dance"
}
// mutations函數不用常量名
// this.$store.commit("update", shareData);
this.update(shareData);
// mutations函數用常量名
// this.$store.commit(mutationTypes.USER_UPDATE, shareData);
}
}
}
</script>
這樣重新命名一下的話,原來這樣調用
this.$store.commit("update", shareData);
現在就可以簡化成了
this.update(shareData);
這個怎麼說呢,因爲還要在methods裏面再用mapMutations重新命名一下,so…就也沒有覺得有簡化(/笑哭)
這裏用mapMutations的時候遇到了一個問題解決了
Module build failed: SyntaxError:
這個樣子滴
我去百度了,說是要改.babelrc文件。
然後我看了一下我的項目目錄,竟然沒得.babelrc文件
我記得這個是腳手架建項目生成的,可能是我開始建項目的時候不是用的
vue init webpack my_vuejs_project
也可能是這個文件上傳git的時候沒加進去,就丟失了。
反正我又重新跑了一下這個命令
vue init webpack my_vuejs_project
把配置文件跟現在的替換了一下,用mapMutations的時候就不報上面的那個錯了。
vue組件通信先寫到這裏吧,我有看到vue3加入了新的特性,後面研究一下再來補充,先去做別的事情啦~