VUEJS项目实践八之组件间通信(包括vuex)

前几话说到用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的时候就不报上面的那个错了。

本文涉及所有完整代码github链接

vue组件通信先写到这里吧,我有看到vue3加入了新的特性,后面研究一下再来补充,先去做别的事情啦~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章