Hazel Wu | 22'mm

Vue 元件之間的傳話筒 總整理

2019-03-12

使用 Vue 建構網站,會越來越多元件,元件之間的溝通非常重要,常常需要傳遞資訊,讓彼此知道,讓資訊流通是一件非常重要的事!

  1. Props 父傳子
    • Parent => Child Communication
  2. \$emit 子傳父
    • 自定義事件,Child => Parent Communication
  3. Event Bus
    • 類似於 Angular 2 的 services 用法

Props 父傳子

用 Props 來傳遞資料,主元件要在外圍寫要傳什麼值進去,像是下面的範例,父元件傳了 name 給子元件值是 Hazel。

1
2
// 父元件 User.vue
<user-detail :myName="Hazel"></user-detail>

子元件要新增 props 屬性來接收資料,接收到的 name 可以直接使用在其他地方,像是下面範例,接收到從父元件傳來的 name,並使用在switchName()的Methods裡。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 子元件 UserDetail.vue
<template>
<p> User Name: {{ switchName() }} </p>
</template>

export default {
props: ['myName'],
methods: {
switchName(){
return this.myName.split('').reverse().join('')
}
}
}

限制 Props 資料類型

Prop 還能夠驗證資料型態,比如說在子元件下我們可以這樣寫

1
2
3
4
5
6
7
// 驗證 Props
props: ['myName', 'age', 'book']
props: {
'myName': String,
'age': Number,
'book': Object
}

當 myName 值傳來不是 String 格式便會報錯,會在 Console.log 提示開發者由於傳遞錯誤的型態。
我們可以用這種方式來驗證資料,以避免程式出錯。

Emit 子傳父

剛剛介紹了 Prop:父元件傳遞資料給子元件,但當子元件想要傳遞資料給父元件,該如何傳遞?我們會用到 emit 客製化事件來做到這件事

1
$emit('事件名稱', 要傳遞的值)

以下程式碼做的處理

  1. 從子元件接收到 myName 的值
  2. 子元件內 reset myName 值
  3. 回傳給父元件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// UserDetail.vue 子元件
export default {
props: {
myName: {
type: String
}
},
methods: {
switchName() {
return this.myName
.split('')
.revverse()
.join('');
},
resetName() {
this.myName = 'Hazel';
this.$emit('nameWasReset', this.myName);
}
}
};

子元件用nameWasReset事件傳遞
要接收訊息的父元件也要設定,將子元件設定的事件名稱用@當前綴
@nameWasReset="name = $event",並存在父元件的資料 name 內。

小教學:關於 Vue 的\$event

$event 為 Vue內的特殊用法,相同於 JavaScript的 event
$event 為子元間傳遞出來的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// User.vue 父元件
<template>
<user-detail :myName="name" @nameWasReset="name =$event"></user-detail>
</template>

<script>
export default {
data(){
return {
name: 'Hazel'
}
}
}
</script>

Vue 單向資料流

單向資料流定義:只能從一個方向來修改,可以由父元件傳遞資料給子元件,但子元件無法反過來直接改變父元件的資料,會引起報錯。也就是只能父元件影響子,子無法影響父。

總結一句話:單向資料流只能父傳子、子傳父。

Unidirectional Data Flow from top to bottom.

如果 A 的子元件想傳遞給 B 的子元件,平行傳資料是可行的嗎?
在 Vue 內,由於單向資料流的關係,無法子傳給子,是無法平行傳資料。

CallBack Function

透過 Props 傳遞 function 也能做到元件溝通
父元件利用 Props 傳遞 resetFn(),值帶入 methods 的 resetName()方法給子元件。
而子元件則用 Props 接收。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 父元件
<template>
<user-detail :myName="name" @nameWasReset="name =$event" :resetFn="resetName"></user-detail>
</template>

<script>
import UserDetail from './UserDetail.vue';
export default {
data(){
return {
name: 'Hazel'
}
},
resetName(){
this.name = 'Hazel';
}
},
components: {
UserDetail: UserDetail,
}
}
</script>

下列程式碼是子元件接受來自父元件的resetFn(),型態是 Function。
當點擊按鈕時會呼叫resetFn(),而值則是從父元件傳遞來的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 子元件
<template>
<button @click="resetFn()">Reset Function</button>
</template>

<script>
export default {
props: {
myName: {
type: String
},
resetFn: Function,
}
}
</script>

兄弟(Sibling)元件間的傳值

Communication between sibling components

假設我們今天有 A 與 B 的子元件,要如何實行 A 與 B 間的傳遞?

  • 父元件:User
  • A 子元件:UserEdit
  • B 子元件:UserDetail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 父元件 User 傳遞值
<template>
<user-edit :userAge="age" @ageWasEdited="age = $event"></user-edit>
<user-detail :userAge="age"><user-detail>

<p>User Age: {{userAge}}</p>
</template>


<script>
export default {
data(){
return {
age: '25'
}
}
}
</script>

A 與 B 子元件皆接收 userAge 資料,並驗證 Number 值
A 元件用\$emit:ageWasEdited 傳回 age 值為 30 給父元件
B 元件也會一併跟著更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// A 子元件:UserEdit
<template>

<p>User Age: {{userAge}}</p>
<button @click="editAge">Edit Age</button>
</template>

<script>
export default {
props: {
userAge: Number
},
methods: {
editAge(){
this.userAge = 30;
this.$emit('ageWasEdited', this.userAge);
}
}
}
</script>

以下是 B 元件 UserDetail ,B 元件作用為 接收父元件的 UserAge 值

1
2
3
4
5
6
7
8
9
10
11
// B 子元件:UserDetail
<template>
<p>User Age: {{userAge}}</p>
</template>
<script>
export default {
props: {
userAge: Number
}
}
</script>

從上面範例可以看出,當我們需要平行傳遞兄弟間的元件資料,可以用以下做法。

  1. 父用 props 傳遞給 A 子元件
  2. A 子元件 子用 \$emit 回傳父,改變父資料
  3. 再由父傳給另一個 B 子元件

Event Bus:溝通橋樑

  • Event Bus 的定位是事件通知。
  • Event Bus 使用時機:有件事要被很多人知道,可以用 Event Bus 來做傳遞,當傳話筒的角色,也可以形容成溝通橋樑的概念。
  • EventBus 像元件一樣,擁有 data, methods 等屬性可以使用。
  • Event Bus 透過 $emit 來發送資料、用 $on 來接收訊息。

下列的範例會示範 A 子元件與 B 子元件的溝通,但不會傳值到父元件。

  1. 在 main.js 建立一個 Vue instance 叫 eventBus
  2. 要使用 event bus 傳遞訊息的元件上,import event bus 進來
  3. 在元件的 methods 上使用 eventBus.$emit 傳遞值

步驟一:創建 eventBus 的 instance
注意:要把 eventBus 建立在 new Vue 上,否則會報錯。

1
2
3
4
5
6
// main.js
export const eventBus = new Vue();
new Vue({
el: '#app',
render: h => h(App)
});

步驟二:要使用傳遞資料的元件上,import eventBus

1
2
// userEdit 元件
<script>import {eventBus} from '../main';</script>

步驟三:用 eventBus.$emit 傳遞值
eventBus 是一個 Vue instance,而 Vue instance 內建有 $emit 的 methods 可以用。

eventBus.$emit('事件名稱', 值)

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
import { eventBus } from '../main';
export default {
props: ['userAge],
methods: {
editAge(){
this.userAge = 30;
// this.$emit('ageWasEdited', this.userAge) 第二種方法:$emit傳值
eventBus.$emit('ageWasEdited', this.userAge) // 第三種方法:eventBus傳值
}
}
}
</script>

B 子元件 要接收值,也需要 import eventBus,而接收值要使用到\$on

eventBus.$on('事件名稱', callback function)
接收 ageWasEdited 事件,回傳的 data 用 callback function,再針對 data 做一系列的處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// B子元件 UserDetail
<script>
import { eventBus } from '../main';
export default {
props: {
userAge: Number
}
created(){
eventBus.$on('ageWasEdited', (age) => {
console.log(age);
this.userAge = age; // 將傳來的age存進userAge內
});
}
}
</script>

EventBus 方法

eventBus 除了可以讓子元件平行溝通之外,也能建立 methods 方法,並讓元件們共用。

  1. 建立 eventBus 的 Vue instance,建立 changeAge 的 methods
  2. 在要使用的子元件上 import eventBus

main.js 上建立 eventBus,並設定 changeAge 方法,接收參數 age

1
2
3
4
5
6
7
8
// main.js
export const eventBus = new Vue({
methods: {
changeAge(age) {
this.$emit('ageWasEdited', age);
}
}
});

子元件 UserEdit.vue 利用 eventBus 的 changeAge 方法,傳遞 userAge 資料

1
2
3
4
5
6
7
8
9
10
import { eventBus } from '../main';
export default {
props: [userAge],
methods: {
editAge() {
this.userAge = 30;
eventBus.changeAge(this.userAge);
}
}
};

同樣的 eventBus 也像元件一樣也擁有 data 的屬性可儲存,這可以讓我們自由運用。

EventBus 缺點:佔用記憶體空間

使用 EventBus 要注意的是,可以將 EventBus 當成是一個廣播電台,開了某個頻道(事件)給想要的人(元件)聽
但當要聽的人(元件)不聽廣播(不使用)了,要把廣播關掉,不回收事件便會殘留記憶體,佔用空間。
要在beforeDestroy時關閉廣播(也就是刪除事件),尤其是在 SPA 上,這些效能運用是非常斤斤計較的!

關閉廣播、回收記憶體

1
eventBus.$off('事件', '事件名稱');
1
2
3
4
5
6
7
8
9
10
11
export default {
methods: {
editAge(){
this.userAge = 30;
eventBus.changeAge(this.userAge);
},
},
beforeDestroy(){
eventBus.$off(ageWasEdited);
}
}

結論

  • props:父傳子
  • emit:子傳父
  • eventBus:使用上非常方便,但維護性不高,最好把握幾個原則
    1. 僅限於同層的兄弟元件通訊,父子通訊透過 Props / \$emit 來進行溝通
    2. eventBus 缺點是記憶體會殘留,有監聽的地方要記得去清除,像是 beforeDestroy 等。
    3. 專案規模很大,可以考慮用 vuex 來管理共同狀態。

以上重整理 Vue 元件間如何傳遞資料,若有誤歡迎留言告訴我,謝謝。

Reference

閱讀量