前几话说到用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加入了新的特性,后面研究一下再来补充,先去做别的事情啦~