Vue 元件間的通訊

Deepspace_發表於2019-01-26

這一節我們一起看看 vue 中元件間的資料是如何傳遞的。

前面,我們已經初步建立了 vue 元件化的思想,知道如何建立元件、引入元件以及如何在元件裡的一些功能。接下來,我們來學習怎麼建立元件之間的連線,也就是元件的通訊。直白一點說就是:在一個元件中做的操作如何更新到應用程式中的其他元件。

這篇文章會從兩個方便介紹 vue 元件間的通訊:

- 父子元件之間的通訊
- 兄弟元件之間的通訊
複製程式碼

一、父子元件之間的通訊

1、父元件向子元件通訊

vue 中,將資料從父元件傳遞到子元件,可以用 props 來實現(這一點,在 React 中也是如此)。

props 指的是從外部(父元件)設定的屬性。同時,為了告訴 vue 子元件需要從自已的外部(父元件)接收資料,需要在子元件的 vue 物件中設定 props 屬性。這個屬性是一個 String 陣列,每個字串表示一個可以從父元件接收的屬性。

我們需要做兩件事情:父元件使用屬性繫結、子元件使用 props 物件接收。 看個例子:

  • 父元件使用屬性繫結 :
<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 上繫結了幾個屬性:

<childCom :list='list' :run='run' :home='this'></childCom>
複製程式碼

繫結屬性的時候,屬性名前需要加冒號。這裡我們繫結了三個屬性,父元件的 data 中的 listmethods 中的 run 方法以及指向父元件的 this

  • 子元件使用 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 ,開啟瀏覽器會看到:

父元件向子元件傳遞

這樣,在子元件中就拿到了父元件傳遞下來的資料和方法以及父元件本身,點選按鈕就可以檢視到父元件傳遞給子元件的資料。

2、子元件向父元件通訊

前面我們知道了父元件如何向子元件通訊,那子元件如何向父元件通訊呢?這裡介紹兩種方式:

  • 子元件觸發事件,父元件監聽事件並作出資料改變
  • 父元件將變更資料的方法以 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 :

子元件向父元件傳遞

二、兄弟元件之間的通訊

vue 中實現兄弟元件間的通訊主要有兩種方法:**通過父元件進行兄弟元件之間通訊、通過 EventHub 進行兄弟元件間通訊。**為了不和上面講述的父子元件之間通訊的程式碼混淆,這裡我們重新新建元件來演示:

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

1、通過父元件進行兄弟元件之間通訊

簡單來說,就是讓兄弟元件通過一個共同父元件進行通訊。

首先建立父元件 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 中把這種方式被稱為狀態提升)。

2、通過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>
複製程式碼

開啟瀏覽器,可以看到這樣也實現了兄弟元件之間的通訊。

三、全域性模式

這裡的全域性模式指的是建立全域性變數和全域性方法,讓其他元件之間共享資料儲存的模式。我們看看怎麼操作:

先建立一個 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>
複製程式碼

效果如下:

全域性模式

可以看到,我們使用這種方式也可以實現元件間的通訊。

四、總結

最後,我們畫個圖總結一下 Vue 元件間的通訊:

Vue元件間的通訊

本節內容程式碼地址:github.com/IDeepspace/…

歡迎大家關注我的部落格:togoblog.cn/

相關文章