# [ 想入門,我陪你 ] Re Vue 重頭說起 | Day 11:組件自定事件與通知 Custom Event ###### tags: `Vue`、`Re:Vue 重頭說起`、`Alex 宅幹嘛` [Document link - Event-Names](https://vuejs.org/v2/guide/components-custom-events.html#Event-Names) :::success **compoenent 與 props 命名種類** camel case -> basicComponent pascal case -> BasicComponent kebab case -> basic-component snake case -> basic_component ::: ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <basic-component @component-event="eventHandler" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler"> BasicComponent </div> `, methods: { clickHandler() { this.$emit('component-event') } } }) </script> </body> </html> ``` :::warning 8:25: event names don’t provide any automatic case transformation. * 事件名稱命名什麼,使用時就要用什麼,因為在html沒大小寫之分,Vue不會幫你自懂轉換大小寫或 case * 官方建議用 kebab-case 當作事件名稱 ::: [Document link - Customizing-Component-v-model](https://vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model) > *v-model 是 deractive ,可以綁在 vue template(Ex: form表單的DOM、模組、component)* > New in 2.2.0+ ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <basic-component v-model="test" @component-event="eventHandler" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler"> BasicComponent </div> `, methods: { clickHandler() { this.$emit('component-event') } } }) </script> </body> </html> ``` v-model 在預設情況會綁 value 這個 參數($attrs) 在 DOM 上 ![](https://i.imgur.com/WNlavAC.png) 在 component內綁參數,串資料 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <basic-component :active="test" @component-event="eventHandler" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler"> BasicComponent </div> `, props: { active: { type: Bealoon default: true } }, methods: { clickHandler() { this.$emit('component-event') } } }) </script> </body> </html> ``` ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <!-- 使用 v-model 就不用綁事件--> <basic-component v-model="test" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler"> BasicComponent </div> `, model: { prop: 'active', // default is value(input) or checked(radio) event: 'component-event' // default is input(input) or change(radio) }, active: { type: Bealoon default: true } methods: { clickHandler() { // 每按一次,把反向 active 傳送出去 this.$emit('component-event', !this.active) } } }) </script> </body> </html> ``` 放便達成快速的雙向綁定: 點擊文字 test 的 true/false 會切換 ![](https://i.imgur.com/d4bf4vS.png) :::warning 25:40 解釋為何要用這個方法 ::: #### 補充 - v-model 原理 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <!-- 雙向綁定 --> <basic-component v-model="test" /> <!-- 單向綁定 --> <basic-component :value="test" @input="inputHandler"/> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler"> BasicComponent </div> `, props: { value: { type: Bealoon, default: false } } methods: { clickHandler() { this.$emit('input', !this.active) } } }) </script> </body> </html> ``` [Document link - Binding Native Events to Components](https://vuejs.org/v2/guide/components-custom-events.html#Binding-Native-Events-to-Components) @click 綁在 component 上無反應是因為,++只要事件綁在component,事件預設都當作 custom event++ 所以要用 **.native* 讓 custom event 變成 native event ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <!-- 使用 v-model 就不用綁事件--> <basic-component v-model="test" @click.native="eventHandler /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler"> BasicComponent </div> `, model: { prop: 'active', event: 'component-event' }, props: { active: { type: Boolean, default: false, }, }, methods: { clickHandler() { this.$emit('component-event', !this.active) } } }) </script> </body> </html> ``` 也可以用 `v-on="$listeners"` 來達成(較少用) ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <!-- 使用 v-model 就不用綁事件--> <basic-component v-model="test" @click="eventHandler /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" v-on="$listeners"> BasicComponent </div> `, model: { prop: 'active', event: 'component-event' }, props: { active: { type: Boolean, default: false, }, }, methods: { clickHandler() { this.$emit('component-event', !this.active) } } }) </script> </body> </html> ``` [Document link - sync-Modifier](https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier) > New in 2.3.0+ #### 補充 - 雙向綁定在客製化模組的做法 大量雙向 -> 直接把屬性放物件綁定 少量雙向(2~3) -> 用 .sync 一個 -> 用 v-model 客製話 因為元件內太多雙向綁定,會搞不清楚props與data之間是誰在控制誰,所以原本的機制: 內通知外,外幫你修改資料,傳回內部,會改成: update:myPropName 機制,幫你自動更新 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <basic-component v-model="test" :title="title" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler> BasicComponent {{ title }} </div> `, model: { prop: 'active', event: 'component-event' }, props: { active: { type: Boolean, default: false, }, title: { type: String, require: true } }, methods: { clickHandler() { this.title = 'New Title' } } }) new Vue({ el: '#app', data() { return { test: true, title: 'Hello World' } }, metohds: { eventHandler() { console.log('this is component event') } } }) </script> </body> </html> ``` 當點擊文字,會修改到 props,雖然會動,但有警告 ![](https://i.imgur.com/bWuTMeX.jpg) 外層不變,還是 Hello World ![](https://i.imgur.com/omwXUtU.png) 內層被改成 New Title ![](https://i.imgur.com/A1N8CuQ.png) 如何避免手動改 Props? #### 以往的做法: 打一個事件出去,給上層資料,走一個基本的事件流程(很麻煩) ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <basic-component v-model="test" :title="title" @title-event="eventHandler" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler> BasicComponent {{ title }} </div> `, model: { prop: 'active', event: 'component-event' }, props: { active: { type: Boolean, default: false, }, title: { type: String, require: true } }, methods: { clickHandler() { this.$emit('title-event', 'New Title') this.$emit('component-event', !this.active) } } }) new Vue({ el: '#app', data() { return { test: true, title: 'Hello World' } }, metohds: { eventHandler(val) { this.title = val; } } }) </script> </body> </html> ``` 點下去打了兩個事件,且無警告,內外元件的title也改了 ![](https://i.imgur.com/SrO5Qan.png) #### .sync 作法 ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <!-- 就不用事件偵聽 --> <basic-component v-model="test" :title.sync="title" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler @title-event="eventHandler"> BasicComponent {{ title }} </div> `, model: { prop: 'active', event: 'component-event' }, props: { active: { type: Boolean, default: false, }, title: { type: String, require: true } }, methods: { clickHandler() { this.$emit('update:title', 'New Title') this.$emit('component-event', !this.active) } } }) new Vue({ el: '#app', data() { return { test: true, title: 'Hello World' } }, metohds: { <!-- event 也不用寫 --> // eventHandler(val) { // this.title = val; // } } }) </script> </body> </html> ``` > event 一樣照打,但少寫了 event listener 與 event handler(方便快速) :::warning - .sync 後面不能寫 expression Ex: `v-bind:title.sync=”doc.title + ‘!’”` - .sync 後面不能寫 literal object Ex: `v-bind.sync=”{ title: doc.title }”` - .sync 可以綁物件!! ::: .sync 可以綁物件 -> 範例如下: ```htmlmixed= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>Day 11</title> </head> <body> <div id="app"> <!-- 一次把 content 每個 key 都開啟 sync 功能 --> <basic-component v-model="test" v-bind.sync="content" /> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script> <script> Vue.component('BasicComponent', { template: ` <div class="basic-component" @click="clickHandler @title-event="eventHandler"> BasicComponent {{ title }} </div> `, model: { prop: 'active', event: 'component-event' }, props: { active: { type: Boolean, default: false, }, title: { type: String, require: true } }, methods: { clickHandler() { this.$emit('update:title', 'New Title') this.$emit('component-event', !this.active) } } }) new Vue({ el: '#app', data() { return { test: true, content: { title: 'Hello World', description: 'Hello World' } } }, metohds: { <!-- event 也不用寫 --> // eventHandler(val) { // this.title = val; // } } }) </script> </body> </html> ``` 思考:傳入屬性到 component 內要以一個物件置入,還是一個屬性一個傳入