這一節我們一起看看 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
中的 list
、methods
中的 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
- 兩個兄弟元件:
BrotherCard
和SisterCard
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
元件包含了 SisterCard
和 BrotherCard
兩個子元件,而且這兩個子元件是兄弟元件。
首先在 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;
複製程式碼
在 store
的 state
中存放了一個 numbers
陣列和一個 addNumber
方法。接下來我們建立兩個元件:
NumberDisplay
元件:用來顯示來自store
的numbers
陣列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
元件間的通訊:
本節內容程式碼地址:github.com/IDeepspace/…
歡迎大家關注我的部落格:togoblog.cn/