# [ 想入門,我陪你 ] 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 上

在 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 會切換

:::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,雖然會動,但有警告

外層不變,還是 Hello World

內層被改成 New Title

如何避免手動改 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也改了

#### .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 內要以一個物件置入,還是一個屬性一個傳入