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加入了新的特性,後面研究一下再來補充,先去做別的事情啦~

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