Vue Component 的 Prop,只能解決資料由外層 Component 傳給內層 Component,若要將資料從內層 Component 傳到外層 Component 呢 ? 此時就要使用 Event。
Version
macOS Catalina 10.15.6
WebStorm 2020.2
Vue 2.6.11
Custom Event
按下內層 component 的 Click Me
,外層 component 將顯示內層 component 傳出的 10
。
App.vue
<template>
<div>
<my-button @my-click="outerClick"></my-button>
<p>{{ msg }}</p>
</div>
</template>
<script>
import myButton from '@/components/MyButton'
let outerClick = function(v) {
this.msg = v
}
export default {
name: 'App',
components: {
myButton
},
data: () => ({
msg: ''
}),
methods: {
outerClick
}
}
</script>
第 3 行
<my-button @my-click="outerClick"></my-button>
使用 MyButton
component,並對 my-click
event 綁定到 outerClick()
。
Event binding 要加 event 名稱前加上
@
,event handler 名稱不必加上()
11 行
let outerClick = function(v) {
this.msg = v
}
定義 my-click
event 的 handler,會接收 event 傳出的資料 並指定在 msg
model 顯示。
MyButton.vue
<template>
<button @click="innerClick">Click Me</button>
</template>
<script>
let innerClick = function() {
this.$emit('my-click', 10)
}
export default {
name: 'MyButton',
methods: {
innerClick
}
}
</script>
第 2 行
<button @click="innerClick">Click Me</button>
定義 MyButton
component 的 HTML template,注意其 <button></button>
的 click
event 綁定到 MyButton
自己的 innerClick()
。
第 6 行
let innerClick = function() {
this.$emit('my-click', 10)
};
使用 Vue Instance 所提供的 $emit()
發出 event。
- 第 1 個 argument 為 event 名稱
- 第 2 個 argument 為想透過 event 所傳出的值
- 若有多個值,可繼續其他參數
Event 名稱必須為 kebab-case,不能使用 CamelCase 或 camelCase,這裏 Vue 並不會如 prop 自動幫你轉成 kebab-case
MyCounter Again
- 按
+1
會對內層 counter + 1 - 按
Emit Counter
會將內層 counter 傳給外層顯示
App.vue
<template>
<div>
<my-counter :start-counter="outerCounter" @emit-counter="showCounter"></my-counter>
<h1>{{ outerCounter }}</h1>
</div>
</template>
<script>
import myCounter from '@/components/MyCounter.vue'
let showCounter = function(v) {
this.outerCounter = v
}
export default {
name: 'App',
components: {
myCounter
},
data: () => ({
outerCounter: 0
}),
methods: {
showCounter
}
}
</script>
第 3 行
<my-counter :start-counter="outerCounter" @emit-counter="showCounter"></my-counter>
使用自定義的 MyCounter
component。
- 將
outerCounter
model 傳進startCounter
prop 當初始值 - 捕捉
emit-counter
event 顯示傳出的 counter 值
第 4 行
<h1>{{ outerCounter }}</h1>
在外層 component 顯示內層 component 的 counter。
11 行
let showCounter = function(v) {
this.outerCounter = v
}
由外層 component 的 showCounter()
接收 emit-counter
event,接收內部傳出的 counter
值給 outerCounter
model 顯示。
MyCounter.vue
<template>
<div>
<h1>{{ innerCounter }}</h1>
<button @click="onAddCounter">+1</button>
<button @click="onEmitCounter">Emit Counter</button>
</div>
</template>
<script>
let onAddCounter = function() {
this.innerCounter++
}
let onEmitCounter = function() {
this.$emit('emit-counter', this.innerCounter)
}
export default {
name: 'MyCounter',
props: [
'startCounter'
],
data: function() {
return {
innerCounter: this.startCounter
}
},
methods: {
onAddCounter,
onEmitCounter
}
};
</script>
第 1 行
<div>
<h1>{{ innerCounter }}</h1>
<button @click="onAddCounter">+1</button>
<button @click="onEmitCounter">Emit Counter</button>
</div>
定義 MyCounter
component 的 HTML template,注意有兩個 <button/>
,分別對應到 onAddCounter()
與 onEmitCounter()
。
onAddCounter()
負責innerCounter
的累加onEmitCounter()
負責發出 event 通知外層 component
20 行
props: [
'startCounter'
],
宣告 startCounter
prop,讓外層藉由 startCounter
prop 傳入 counter 的初始值。
23 行
data: function() {
return {
innerCounter: this.startCounter
}
},
定義 innerCounter
model,由 startCounter
prop 定義其初始值。
10 行
let onAddCounter = function() {
this.innerCounter++
}
對 innerCounter
model 做累加。
14 行
let onEmitCounter = function() {
this.$emit('emit-counter', this.innerCounter)
}
這行是關鍵,對外發出 emit-counter
event,並傳出目前的 innerCounter
model。
sync Modifier
回想剛剛的 MyCounter
範例:
MyCounter
component 透過emit-counter
event 傳出目前innerCounter
model- 外層再透過
showCounter()
接收emit-counter
event 傳來的值 - 將 event 接收值指定到
outerCounter
model - HTML template 顯示
outerCounter
model
實務上這種內層 component 的 model 一改變,外層的 model 也要立即改變的應用非常多,Vue 另外提供了 sync
modifier,可以少寫很多 code。
App.vue
<template>
<div>
<my-counter :start-counter.sync="outerCounter"></my-counter>
<h1>{{ outerCounter }}</h1>
</div>
</template>
<script>
import myCounter from '@/components/MyCounter.vue'
export default {
name: 'App',
components: {
myCounter
},
data: () => ({
outerCounter: 0
})
}
</script>
第 3 行
<my-counter :start-counter.sync="outerCounter"></my-counter>
一樣透過 start-counter
prop 傳入 outerCounter
model,但多了 sync
modifier,表示將來 MyCounter
component 內部 model 有任何改變,則 outerCounter
model 會同步跟著改變,已經不需要綁定 showCounter()
。
MyCounter.vue
<template>
<div>
<h1>{{ innerCounter }}</h1>
<button @click="onAddCounter">+1</button>
<button @click="onEmitCounter">Emit Counter</button>
</div>
</template>
<script>
let onAddCounter = function() {
this.innerCounter++
}
let onEmitCounter = function() {
this.$emit('update:startCounter', this.innerCounter)
}
export default {
name: 'MyCounter',
props: [
'startCounter'
],
data: function() {
return {
innerCounter: this.startCounter
}
},
methods: {
onAddCounter,
onEmitCounter
}
}
</script>
14 行
let onEmitCounter = function() {
this.$emit('update:startCounter', this.innerCounter)
}
由原本 emit emit-counter
event,改成 update:startCounter
event,第二個參數一樣傳出 innerCounter
model。
Vue 規定若要使用
sync
modifier,必須 emitupdate:prop
這種格式的 event,如此sync
modifer 才收得到
v-model Directive
sync
看起來已經很像 two way binding,可以使用更精簡的 v-model
directive 。
App.vue
<template>
<div>
<my-counter v-model="outerCounter"></my-counter>
<h1>{{ outerCounter }}</h1>
</div>
</template>
<script>
import MyCounter from '@/components/MyCounter.vue'
export default {
name: 'App',
components: {
MyCounter
},
data: () => ({
outerCounter: 0
})
}
</script>
第 3 行
<my-counter v-model="outerCounter"></my-counter>
直接在 MyCounter
component 使用 v-model
directive,並綁定到 outerCounter
model。
MyCounter.vue
<template>
<div>
<h1>{{ innerCounter }}</h1>
<button @click="onAddCounter">+1</button>
<button @click="onEmitCounter">Emit Counter</button>
</div>
</template>
<script>
let onAddCounter = function() {
this.innerCounter++
}
let onEmitCounter = function() {
this.$emit('input', this.innerCounter);
}
export default {
name: 'MyCounter',
props: [
'value'
],
data: function() {
return {
innerCounter: this.value
}
},
methods: {
onAddCounter,
onEmitCounter
}
}
</script>
20 行
props: [
'value'
],
v-model
directive 預設使用 value
prop,所以要自行宣告。
14 行
let onEmitCounter = function() {
this.$emit('input', this.innerCounter);
}
v-model
directive 預設接收 input
event,所以要自行 emit。
要使用 v-model
directive,要遵循 Vue 的兩項規定:
- 使用
value
prop - 使用
input
event
sync
modifier 與v-model
directive 兩者功能相同,但v-model
觀念比較容易理解,程式碼也比較少,個人偏好v-model
native Modifier
目前外層可以透過內層 component 發出的 custom event 接收到 data,但如第一個 MyButton
component,其實是內層 component 監聽 click
DOM event 並且發出自己的 my-click
custom event。
既然源頭是 click
DOM event,外層 component 可直接綁定到內層 component 的 click
DOM event。
App.vue
<template>
<div>
<my-button @click.native="outerClick"></my-button>
<p>{{ msg }}</p>
</div>
</template>
<script>
import MyButton from '@/components/MyButton.vue'
let outerClick = function() {
this.msg = 'Button clicked'
}
export default {
name: 'App',
components: {
MyButton
},
data: () => ({
msg: ''
}),
methods: {
outerClick
}
}
</script>
第 2 行
<my-button @click.native="outerClick"></my-button>
想直接綁定 MyButton
的 click
DOM event,由於是原生 event,只要在 @click
加上 native
modifier,Vue 就會幫你將 outerClick()
直接綁定到原生的 click
DOM event。
11 行
let outerClick = function() {
this.msg = 'Button clicked'
}
由於 outerClick()
直接綁定到 DOM event,所以就收不到內層 MyButton
component 所傳出的資料,因此稍作修改。
MyButton.vue
<template>
<button>Click Me</button>
</template>
<script>
export default {
name: 'MyCounter',
}
</script>
也因為 click.native
直接綁定到原生的 DOM event,因此就不需要另外 emit event 了。
Conclusion
- 透過 event 能使 mode 由內層 component 傳到外層 component
sync
modifier 讓我們直接將 component 內外 model 同步v-model
directive 算是sync
modifier 的簡化版,讓我們更直覺實現 component 的 two way bindingnative
modifier 能讓我們直接綁定 component 內的 DOM event,不用再自行 emit event