---
title: 'VueJS 2.0 教學筆記: 父子組件之間的溝通 Prop & Emit'
disqus: hackmd
---
VueJS 2.0 教學筆記: 父子組件之間的溝通 Prop & Emit
===
綱要:
[TOC]
完整範例:
[父組件](https://github.com/fortes1219/vue_2020/blob/class/day_4/src/components/PropsEmit.vue) [子組件](https://github.com/fortes1219/vue_2020/blob/class/day_4/src/components/panel/ChildPanel.vue)
---
用一個外部載入的組件,可以讓組件上的白色圈圈控制移動到不同座標,來學習 Prop 和 Emit 讓父子組件之間資料能溝通的方法。
![](https://i.imgur.com/oKtiWmw.png)
檔案結構與路由
---
**1. 建立新的 vue 檔案**
在 `components` 下新增一個 vue 檔案命名為`PropsEmit.vue` 以及一個目錄 `panel`,並在該目錄下新增 `ChildPanel.vue`。
**2. 設定路由**
如下圖示,替 `PropsEmit.vue` 設置路由,而 `ChildPanel.vue` 是另行引入的子組件,此範例中不需要為子組件設置路由。
![](https://i.imgur.com/h8Lgrke.png)
子組件的建構
---
子組件本身帶有三個Button,分別是 Mode A、Mode B 與 Mode C 以及一個顯示白點座標的Panel。
我們要將Mode Button的事件以 `emit` 的方式傳給父組件:
![](https://i.imgur.com/4IOVX1m.png)
```htmlmixed=
<template>
<div class="row">
<div class="row horizontal v_center">
<!--emitModeA~C 後面的參數是預設的座標百分比值-->
<el-button @click="emitModeA(10, 50)">Mode A</el-button>
<el-button @click="emitModeB(20, 100)">Mode B</el-button>
<el-button @click="emitModeC(100, 100)">Mode C</el-button>
</div>
<!--Panel上的寬高可以由外部父組件指定Props-->
<div class="panel" :style="{width: panelWidth + 'px', height: panelHeight + 'px' }">
<!--白點座標由子組件的v-model指定預設值,也可以由Button事件或者父組件提供的Props改變-->
<span class="pos_dot" :style="{top: dotTop + '%', left: dotLeft + '%',}"></span>
</div>
</div>
</template>
```
```javascript=
<script>
export default {
name: 'ChildPanel',
// props就是開放給外部父組件指定的名稱與值的型別,必須要指定一個單一型別以及預設值
props: {
panelWidth: {
type: Number,
default: 100,
},
panelHeight: {
type: Number,
default: 100,
},
dotTop: {
type: Number,
default: 0,
},
dotLeft: {
type: Number,
default: 0,
},
},
data() {
return {
};
},
methods: {
// 對應下方Scss樣式的position座標
emitModeA(top, left) {
// 這裏命名的 handleEmitModeA B C等名稱
// 就是父組件以 on-event呼叫子組件函式的依據
this.$emit('handleEmitModeA', top, left);
},
emitModeB(top, left) {
this.$emit('handleEmitModeB', top, left);
},
emitModeC(top, left) {
this.$emit('handleEmitModeC', top, left);
},
},
};
</script>
```
```sass=
<style lang="scss">
.panel {
position: relative;
border: 1px solid #999;
background: rgba(64, 57, 68, 0.7);
transition: all 0.3s ease-in-out;
.pos_dot{
position: absolute;
transform: translateX(-50%) translateY(-50%);
width: 30px;
height: 30px;
border: 1px solid #fff;
border-radius: 50%;
transition: all 0.3s ease-in-out;
}
}
</style>
```
父組件的建構與調用子組件
---
父組件上會有 Reset 和 Center 兩個 Button 做自有事件控制座標,改變 Props 傳入的值之外,也接收子組件 Emit 過來的事件,讓被調用的子組件事件可以在父組件上運作。
![](https://i.imgur.com/sNxFFSD.png)
```htmlmixed=
<template>
<div class="page">
<div class="row horizontal v_center space">
<span class="row">{{ 'Props & Emit' }}</span>
</div>
<div class="row horizontal" data-space="space-vertical">
<!-- 我們引入兩個同樣的組件,兩個組件即使相同,但運作時是分開進行的,這個範例會以同時運作為例 -->
<!--還記得剛才在子組件的 this.$emit('handleEmitModeA', top, left) 吧? -->
<!--用 on-event 指定它跟父組件上的事件parentEmitModeA B C連動-->
<child-panel
@handleEmitModeA="parentEmitModeA"
@handleEmitModeB="parentEmitModeB"
@handleEmitModeC="parentEmitModeC"
:dotTop="dotPos[0].top"
:dotLeft="dotPos[0].left"
/>
<!--panelWidth、panelHeight、dotTop、dotLeft都是子組件中定義的Props-->
<ChildPanel
@handleEmitModeA="parentEmitModeA"
@handleEmitModeB="parentEmitModeB"
@handleEmitModeC="parentEmitModeC"
:panelWidth="400"
:panelHeight="400"
:dotTop="dotPos[1].top"
:dotLeft="dotPos[1].left"
/>
</div>
<div class="row horizontal">
<!--Reset 和 Center 兩個事件都是改變父組件v-model後再將之傳入子組件的Props-->
<el-button @click="resetPos">Reset</el-button>
<el-button @click="setCenter">Center</el-button>
</div>
</div>
</template>
```
```javascript=
<script>
// 引入子組件
import ChildPanel from './panel/ChildPanel.vue';
export default {
name: 'PropsEmit',
// 引入後也要記得在這裡註冊子組件才會變成合法組件
components: {
ChildPanel,
},
data() {
return {
// 組件有兩個,所以用Array包Object的方式分配所屬的v-model
dotPos: [
{ top: 50, left: 50 },
{ top: 10, left: 80 },
],
};
},
methods: {
// reset 事件,將兩個組件的座標都重設為 0, 0
resetPos() {
this.dotPos.forEach((el) => {
el.top = 0;
el.left = 0;
});
console.log('(Parent-Reset) updated dot pos to child');
},
// center 事件,將兩個組件的座標都設為 50, 50
setCenter() {
this.dotPos.forEach((el) => {
el.top = 50;
el.left = 50;
});
console.log('(Parent-Center) updated dot pos to child');
},
// 子組件事件綁定,並且改變指定的座標值
// 假如你想讓每個組件自己獨立運作,就不要使用forEach去改變this.dotPos的內容
// 而是直接指定對應的v-model: this.dotPos[0] 或 this.dotPos[1]
parentEmitModeA(top, left) { // from child: top, left
// v-model: this.dotPos
this.dotPos.forEach((el) => {
el.top = top;
el.left = left;
});
},
parentEmitModeB(top, left) {
this.dotPos.forEach((el) => {
el.top = top;
el.left = left;
});
},
parentEmitModeC(top, left) {
this.dotPos.forEach((el) => {
el.top = top;
el.left = left;
});
},
},
};
</script>
```
本日後記
===
組件之間的溝通不只有父子關係組件,同層級的組件之間除了比較少見的 Event Bus 以外,一般都推薦使用Vuex狀態管理工具來讓每個不同的組件都能同時調用同樣的v-model。
而父子關係組件是屬於比較簡易使用的例子,務必要熟練。
---
###### tags: `VueJS` `Props` `Emit`