---
# System prepended metadata

title: '[ 想入門，我陪你 ] Re Vue 重頭說起 | Day 11：組件自定事件與通知 Custom Event'
tags: [Alex 宅幹嘛, Vue, 'Re:Vue 重頭說起']

---

# [ 想入門，我陪你 ] 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 內要以一個物件置入，還是一個屬性一個傳入