Vue 組件間的通信

這一節咱們一塊兒看看 vue 中組件間的數據是如何傳遞的。javascript

前面,咱們已經初步創建了 vue 組件化的思想,知道如何建立組件、引入組件以及如何在組件裏的一些功能。接下來,咱們來學習怎麼創建組件之間的鏈接,也就是組件的通信。直白一點說就是:在一個組件中作的操做如何更新到應用程序中的其餘組件。css

這篇文章會從兩個方便介紹 vue 組件間的通信:html

- 父子組件之間的通信
- 兄弟組件之間的通信

<!-- more -->vue

1、父子組件之間的通信

一、父組件向子組件通信

vue 中,將數據從父組件傳遞到子組件,能夠用 props 來實現(這一點,在 React 中也是如此)。java

props 指的是從外部(父組件)設置的屬性。同時,爲了告訴 vue 子組件須要從自已的外部(父組件)接收數據,須要在子組件的 vue 對象中設置 props 屬性。這個屬性是一個 String 數組,每一個字符串表示一個能夠從父組件接收的屬性。react

咱們須要作兩件事情:父組件使用屬性綁定、子組件使用 props 對象接收。 看個例子:git

  • 父組件使用屬性綁定 :
<template>
  <div>
    <ChildCom :list='list' :run='run' :home='this'></ChildCom>
  </div>
</template>

<script>
import ChildCom from './ChildCom';

export default {
  data() {
    return {
      list: ['我是父組件裏面的數據', '我來自父組件'],
    };
  },
  components: {
    ChildCom,
  },
  methods: {
    run() {
      alert('我是父組件裏面的方法'); // eslint-disable-line
    },
  },
};
</script>

咱們在父組件 ParentCom 裏面引入了子組件 ChildCom 。爲了將數據從父組件傳到子組件,咱們在子組件 ChildCom 上綁定了幾個屬性:github

<childCom :list='list' :run='run' :home='this'></childCom>

綁定屬性的時候,屬性名前須要加冒號。這裏咱們綁定了三個屬性,父組件的 data 中的 listmethods 中的 run 方法以及指向父組件的 thisvuex

  • 子組件使用 props 對象接收 :

接下來,咱們建立一個 ChildCom 組件,經過子組件的 props 選項來得到父組件傳過來的數據:數組

<template>
 <div>
   <div class='list'>
     <ul>
       <li v-for='item in list' :key='item'>{{ item }}</li>
     </ul>
   </div>
   <div class='buttons'>
     <button @click='run'>執行父組件的方法</button>
     <button @click='getParent()'>獲取父組件的數據和方法</button>
   </div>
 </div>
</template>

<script>
export default {
 methods: {
   getParent() {
     alert(this.home); // eslint-disable-line
     alert(this.home.list); // eslint-disable-line
     alert(this.home.run); // eslint-disable-line
   },
 },
 props: ['list', 'run', 'home'],
};
</script>

<style lang="postcss" scoped>
.list {
 margin-bottom: 10px;
}
li {
 margin: 10px 0;
 list-style: none;
}
button {
 padding: 6px;
 background-color: #35b880;
 border: none;
 color: white;
 font-size: 16px;
 margin: 5px;
}
</style>

子組件的 props 中接收了父組件傳遞下來的屬性。須要注意的是,props 字符串數組中的值(prop)要和在父組件中爲子組件綁定屬性的屬性名保持一致。

這裏咱們加了一些樣式,在 App.vue 中引入父組件 ParentCom ,打開瀏覽器會看到:

父組件向子組件傳遞

這樣,在子組件中就拿到了父組件傳遞下來的數據和方法以及父組件自己,點擊按鈕就能夠查看到父組件傳遞給子組件的數據。

二、子組件向父組件通信

前面咱們知道了父組件如何向子組件通信,那子組件如何向父組件通信呢?這裏介紹兩種方式:

  • 子組件觸發事件,父組件監聽事件並做出數據改變
  • 父組件將變動數據的方法以 props 的形式傳給子組件(借鑑 react 的父子通信方式)
2.1 監聽事件

首先在子組件 ChildCom<template> 中添加一個新的標籤 button。在這個 button 上添加一個click 事件:

<div class='buttons'>
  <!-- add this -->
  <button @click='submit("我是子組件傳遞給父組件的數據")'>子組件觸發更改父組件的數據</button>
</div>

當咱們點擊這個按鈕的時候,想要執行 submit 方法,咱們在子組件的 <script> 中添加這個方法:

methods: {
  getParent() {
    alert(this.home); // eslint-disable-line
    alert(this.home.list); // eslint-disable-line
    alert(this.home.run); // eslint-disable-line
    alert(this.home.appendToList); // eslint-disable-line
  },
  // add this
  submit(text) {
    this.$emit('addItem', text);
  },
},

觸發事件時發出($emit)自定義的事件: addItem ,這裏咱們也給 addItem 事件傳遞了一個 text 參數。這樣就完成了子組件發出自定義事件的過程。

接下來須要在父組件中監聽子組件傳遞的自定義事件 addItem 。怎麼作呢?

在父組件中給子組件綁定監聽子組件中自定義的事件的方法。這就意味着咱們也須要在父組件中定義這個方法。看看代碼:

<template>
  <div>
    <ChildCom :list='list' :run='run' :home='this' @addItem='addItem'></ChildCom>
  </div>
</template>

<script>
import ChildCom from './ChildCom';

export default {
  data() {
    return {
      list: ['我是父組件裏面的數據', '我來自父組件'],
    };
  },
  components: {
    ChildCom,
  },
  methods: {
    run() {
      alert('我是父組件裏面的方法'); // eslint-disable-line
    },
    addItem(item) {
      this.list.push(item);
    },
  },
};
</script>

在子組件上綁定監聽子組件中自定義事件的方法須要使用 @ 符號,在 methods 中添加了 addItem 方法。這時候,咱們打開瀏覽器,點擊第三個按鈕,就會看到子組件向父組件傳遞的數據了。

子組件向父組件傳遞

2.2 傳遞 props

傳遞 props 的意思是說在父組件裏面定義改變父組件數據的方法,而後經過 props 傳遞給子組件,這樣子組件就能夠觸發執行從父組件傳遞下來的方法,達到更改父組件數據的目的。這種方法借鑑了 React 中組件通信的方式。看看代碼:

咱們依舊使用上面的代碼,在 ParentCom 組件中將 addItem 方法傳遞給子組件:

<ChildCom :list='list' :run='run' :home='this' @addItem='addItem' :addItem='addItem'></ChildCom>

在子組件 ChildCom 中添加一個 button ,在它的點擊事件中執行父組件的 addItem 方法,因此,咱們也須要在子組件的 props 選項中把 addItem 方法添加進去:

<template>
  <div>
    <div class='list'>
      <ul>
        <li v-for='item in list' :key='item'>{{ item }}</li>
      </ul>
    </div>
    <div class='buttons'>
      <button @click='run'>執行父組件的方法</button>
      <button @click='getParent()'>獲取父組件的數據和方法</button>
      <button @click='submit("我是子組件傳遞給父組件的數據")'>子組件觸發更改父組件的數據</button>
      <!-- add this -->
      <button @click='addItem("我是經過子組件props方式傳遞給父組件的數據")'>子組件觸發更改父組件的數據-2</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {};
  },
  methods: {
    getParent() {
      alert(this.home); // eslint-disable-line
      alert(this.home.list); // eslint-disable-line
      alert(this.home.run); // eslint-disable-line
      alert(this.home.appendToList); // eslint-disable-line
    },
    submit(text) {
      this.$emit('addItem', text);
    },
  },
  // add this
  props: ['list', 'run', 'home', 'addItem'],
};
</script>

打開瀏覽器,點擊 button :

子組件向父組件傳遞

2、兄弟組件之間的通信

vue 中實現兄弟組件間的通信主要有兩種方法:經過父組件進行兄弟組件之間通信、經過 EventHub 進行兄弟組件間通信。爲了避免和上面講述的父子組件之間通信的代碼混淆,這裏咱們從新新建組件來演示:

  • 父組件: ParentCard
  • 兩個兄弟組件:BrotherCardSisterCard

一、經過父組件進行兄弟組件之間通信

簡單來講,就是讓兄弟組件經過一個共同父組件進行通信。

首先建立父組件 ParentCard

<template>
  <div class='container'>
    <h2>父組件</h2>
    <button @click='stopCommunicate' v-if='showButton'>中止通信</button>
    <div class='card-body'>
      <brother-card :messageSon='messageson' @brotherSaid='messageDaughter' class='card-brother'></brother-card>
      <sister-card :messageDaughter='messagedaughter' @sisterSaid='messageSon' class='card-sister'></sister-card>
    </div>
  </div>
</template>

<script>
import BrotherCard from './BrotherCard';
import SisterCard from './SisterCard';

export default {
  name: 'ParentCard',
  data() {
    return {
      messagedaughter: '',
      messageson: '',
    };
  },
  components: { BrotherCard, SisterCard },
  methods: {
    messageDaughter(message) {
      this.messagedaughter = message;
    },
    messageSon(message) {
      this.messageson = message;
    },
    showButton() {
      return this.messagedaughter && this.messageson;
    },
    stopCommunicate() {
      this.messagedaughter = '';
      this.messageson = '';
    },
  },
};
</script>

<style scoped>
.container {
  width: 70%;
  margin: 10px auto;
  border-radius: 10px;
  box-shadow: 1px 1px 1px 1px rgba(50, 50, 93, 0.1),
    0 5px 15px rgba(0, 0, 0, 0.07) !important;
  padding: 30px;
}
.card-body {
  display: flex;
  justify-content: center;
}
.card-brother,
.card-sister {
  margin: 0 50px;
}
</style>

建立 BrotherCard 組件:

<template>
  <div>
    <div>
      <p>我是子組件:Brother</p>
      <button @click='messageSister'>給妹妹發消息</button>
      <div v-if='messageSon' v-html='messageSon'></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'BrotherCard',
  props: ['messageSon'],
  methods: {
    messageSister() {
      this.$emit('brotherSaid', 'Hi,妹妹');
    },
  },
};
</script>

建立 SisterCard 組件:

<template>
  <div>
    <div>
      <p>我是子組件:Sister</p>
      <button @click='messageBrother'>給哥哥發消息</button>
      <div v-if='messageDaughter' v-html='messageDaughter'></div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'SisterCard',
  props: ['messageDaughter'],
  methods: {
    messageBrother() {
      this.$emit('sisterSaid', 'Hi,哥哥');
    },
  },
};
</script>

結果以下:

兄弟組件之間的通訊

在學習完父子組件之間的通信方法以後,經過父組件進行兄弟組件的通信就很簡單了,其實就是把兄弟之間須要共享的數據提高至他們最近的父組件當中進行管理,將他們的父組件做爲中間媒介(在 React 中把這種方式被稱爲狀態提高)。

二、經過EventHub進行兄弟間組件通信

vue1.0 中,組件之間的通訊主要經過 $dispatch 沿着父鏈向上傳播和經過 $broadcast 向下廣播來實現。可是在 vue2.0$dispatch$broadcast 已經被棄用

vue 中也提供了相似 Redux 的組件通訊和狀態管理方案:vuex。對於中大型的項目來講,使用 vuex 是一個很好的選擇。可是對於小型的項目來講,若是一開始就引入了 vuex ,是徹底不必的。

vue 官方文檔中也給出了$dispatch$broadcast 最簡單的升級方式就是:經過使用事件中心,容許組件自由交流,不管組件處於組件樹的哪一層。 vue 文檔中把這個事件中心命名爲 eventHub,也有不少其餘教程中將其命名爲 eventBus 。在本教程中,咱們統一命名爲 eventHub

咱們一樣基於上面的示例來作修改:ParentCard 組件包含了 SisterCardBrotherCard 兩個子組件,並且這兩個子組件是兄弟組件。

首先在 main.js 文件中定義一個新的 eventHub 對象(vue 實例 ):

import Vue from 'vue';
import App from './App';
import router from './router';

Vue.config.productionTip = false;

// add this
export const eventHub = new Vue(); // eslint-disable-line

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>',
});

接着咱們要作的是讓 eventHub 實例成爲 BrotherCard 組件中發出事件的實例,使用 eventHub.$emit 來替代上例中的 this.$emit(由於 eventHub 是一個 vue 實例,因此它可使用 $emit 方法)。

<template>
  <div>
    <p>我是Brother組件</p>
    <button @click='messageSister'>給妹妹發消息</button>

    <div v-if='fromSister' v-html='fromSister'></div>
  </div>
</template>

<script>
import { eventHub } from '../../main';

export default {
  name: 'BrotherCard',
  data: () => ({
    fromSister: '',
  }),
  methods: {
    messageSister() {
      eventHub.$emit('brotherSaid', 'Hi,妹妹');
    },
  },
  /* eslint-disable */
  created() {
    eventHub.$on('sisterSaid', message => {
      this.fromSister = message;
    });
  },
};
</script>

引入 main.js,而且將 created() 生命週期鉤子添加到 BrotherCard 組件中。在 created() 鉤子函數中添加 eventHub 啓動自定義事件的監聽器,監聽 sisterSaid 這個動做。

接下來咱們改造下 SisterCard 組件,和 BrotherCard 組件的改造是同樣的:

<template>
  <div>
    <p>我是Sister組件</p>
    <button @click='messageBrother' class='btn'>給哥哥發消息</button>
    <div v-if='fromBrother' v-html='fromBrother'></div>
  </div>
</template>

<script>
import { eventHub } from '../../main';

export default {
  name: 'SisterCard',
  data: () => ({
    fromBrother: '',
  }),
  methods: {
    messageBrother() {
      eventHub.$emit('sisterSaid', 'Hi,哥哥');
    },
  },
  /* eslint-disable */
  created() {
    eventHub.$on('brotherSaid', message => {
      this.fromBrother = message;
    });
  },
};
</script>

這時候,咱們就不用在父組件 ParentCard 作任何操做了:

<template>
  <div class='container'>
    <h2>父組件</h2>
    <div class='card-body'>
      <brother-card class='card-brother'></brother-card>
      <sister-card class='card-sister'></sister-card>
    </div>
  </div>
</template>

<script>
import BrotherCard from './BrotherCard';
import SisterCard from './SisterCard';

export default {
  name: 'ParentCard',
  components: {
    'brother-card': BrotherCard,
    'sister-card': SisterCard,
  },
};
</script>

打開瀏覽器,能夠看到這樣也實現了兄弟組件之間的通信。

3、全局模式

這裏的全局模式指的是建立全局變量和全局方法,讓其餘組件之間共享數據存儲的模式。咱們看看怎麼操做:

先建立一個 store.js ,在這個 JS 文件裏建立全局的變量和方法:

const store = {
  state: { numbers: [1, 2, 3] },
  addNumber(newNumber) {
    this.state.numbers.push(newNumber);
  },
};

export default store;

storestate 中存放了一個 numbers 數組和一個 addNumber 方法。接下來咱們建立兩個組件:

  • NumberDisplay 組件:用來顯示來自 storenumbers 數組
  • NumberSubmit 組件:容許用戶向數據數組中添加一個新的數字

建立 NumberDisplay 組件:

<template>
  <div>
    <h2>{{ storeState.numbers }}</h2>
  </div>
</template>

<script>
import store from './store';

export default {
  name: 'NumberDisplay',
  data() {
    return {
      storeState: store.state,
    };
  },
};
</script>

建立 NumberSubmit 組件:

<template>
  <div class='form'>
    <input v-model='numberInput' type='number'>
    <button @click='addNumber(numberInput)'>Add new number</button>
  </div>
</template>

<script>
import store from './store';

export default {
  name: 'NumberSubmit',
  data() {
    return {
      numberInput: 0,
    };
  },
  methods: {
    addNumber(numberInput) {
      store.addNumber(Number(numberInput));
    },
  },
};
</script>

接着在 GlobalMode.vue 中引用剛纔建立的組件:

<template>
  <div>
    <NumberDisplay/>
    <NumberSubmit/>
  </div>
</template>

<script>
import NumberDisplay from '../components/pass-data-3/NumberDisplay';
import NumberSubmit from '../components/pass-data-3/NumberSubmit';

export default {
  name: 'GlobalMode',
  components: { NumberDisplay, NumberSubmit },
};
</script>

效果以下:

全局模式

能夠看到,咱們使用這種方式也能夠實現組件間的通信。

4、總結

最後,咱們畫個圖總結一下 Vue 組件間的通信:

Vue組件間的通信

本節內容代碼地址: https://github.com/IDeepspace...
歡迎關注個人博客: https://togoblog.cn/