--- 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`