Vue: Component Events
2022-03-25
0. Intro
1. Emitting and Listening to Events
Child emitting
Child component 可以直接在 template 里面 emit custom events(用 built-in $emit
function)。
<!-- MyComponent -->
<button @click="$emit('someEvent')">click me</button>
这个 $emit()
function 也可以在 component instance 上使用(即this.$emit()
)。
Parent listening
Parent 可以用 v-on
来监听。
<MyComponent @some-event="callback" />
Component event listener 也支持之前讲过的 event handler modifier 。
<MyComponent @some-event.once="callback" />
Naming convention
正如 components 和 props,event name 也有自动的 case transformation,我们一般是用 camelCase 来定义 emit event,然后在 parent template 用 kebad-cased listener。
Event propagation
和 native DOM events 不同的是,component emitted events 不会冒泡,你只能监听 direct child component emitted events.
Unlike native DOM events, component emitted events do not bubble. You can only listen to the events emitted by a direct child component.
2. Event Arguments
Example - child
如果要在 emit event 的时候附加信息的话,可以加在 function argument 里面。
<button @click="$emit('increaseBy', 1)">Increase by 1</button>
Example - parent
在监听 event 的时候,可以用 inline arrow function 作为 listener。
<MyButton @increase-by="(n) => count += n" />
Example - parent 2
假如 event handler 是 method 的话,可以直接把 method name 作为 value,然后在 js 里面定义这个 method,从 child 传入的 argument 就会成为这个 method 的第一个 parameter。
<MyButton @increase-by="increaseCount" />
The value will be passed as the first parameter of that method.
methods: {
increaseCount(n) {
this.count += n
}
}
All extra arguments passed to
$emit()
after the event name will be forwarded to the listener. For example, with$emit('foo', 1, 2, 3)
the listener function will receive three arguments.
3. Declaring Emitted Events
我们可以用 emits
option 来声明 component emitted events。
Example 1 - array syntax
export default {
emits: ["inFocus", "submit"],
};
Example 2 - object syntax (可以加 runtime validation)
The emits
option also supports an object syntax, which allows us to perform runtime validation of the payload of the emitted events.
export default {
emits: {
submit(payload) {
// return `true` or `false` to indicate
// validation pass / fail
},
},
};
Order
推荐按顺序来 define emitted events。
Although optional, it is recommended to define all emitted events in order to better document how a component should work. It also allows Vue to exclude known listeners from fallthrough attributes .
假如 emit event 跟 native event 冲突,只监听 emit event
If a native event (e.g., click
) is defined in the emits
option, the listener will now only listen to component-emitted click
events and no longer respond to native click
events.
4. Events Validation
如果用 object syntax 就可以加 event validation。
To add validation, the event is assigned a function that receives the arguments passed to the
this.$emit
call and returns a boolean to indicate whether the event is valid or not.
export default {
emits: {
// No validation
click: null,
// Validate submit event
submit: ({ email, password }) => {
if (email && password) {
return true;
} else {
console.warn("Invalid submit event payload!");
return false;
}
},
},
methods: {
submitForm(email, password) {
this.$emit("submit", { email, password });
},
},
};
5. Usage with v-model
v-model 没有用在 component 上时
v-model
例 1 和例 2 的效果一样。
Example 1
<input v-model="searchText" />
Example 2
<input :value="searchText" @input="searchText = $event.target.value" />
v-model 用在 component 上时
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
For this to actually work though, the <input>
inside the component must:
- Bind the
value
attribute to themodelValue
prop - On
input
, emit anupdate:modelValue
event with the new value
Example - Component with v-model
<CustomInput v-model="searchText" />
<!-- CustomInput.vue -->
<script>
export default {
props: ["modelValue"],
emits: ["update:modelValue"],
};
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Example 2 - Component with v-model
通过 computed property 来完成。
Another way of implementing v-model
within this component is to use a writable computed
property with both a getter and a setter. The get
method should return the modelValue
property and the set
method should emit the corresponding event.
<!-- CustomInput.vue -->
<script>
export default {
props: ["modelValue"],
emits: ["update:modelValue"],
computed: {
value: {
get() {
return this.modelValue;
},
set(value) {
this.$emit("update:modelValue", value);
},
},
},
};
</script>
<template>
<input v-model="value" />
</template>
6. v-model
arguments
默认情况下,component 的 v-model
用 modelValue
作为 prop,用 update: modelValue
作为 event。我们可以通过 pass argument to v-model 来改变这些名字。
<MyComponent v-model:title="bookTitle" />
In this case, the child component should expect a title
prop and emit an update:title
event to update the parent value.
<!-- MyComponent.vue -->
<script>
export default {
props: ["title"],
emits: ["update:title"],
};
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
Multiple v-model
bindings
由于 v-model
可以 target 不同的 prop 和 event,所以我们可以绑定多个值。
<UserName v-model:first-name="firstName" v-model:last-name="lastName" />
<!-- UserName.vue -->
<script>
export default {
props: {
firstName: String,
lastName: String,
},
emits: ["update:firstName", "update:lastName"],
};
</script>
<template>
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)"
/>
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)"
/>
</template>
7. Handling v-model
modifiers
v-model
有一些 built-in modifier,比如 .trim
.lazy
等,不过有时候你希望用一个 custom modifier。
Example - create custom modifier .capitalize
<MyComponent v-model.capitalize="myText" />
Modifiers added to a component v-model
will be provided to the component via the modelModifiers
prop. 下面的例子里,我们的 component 包含 modelModifiers
prop,默认是 empty object。由于我们在 parent 里面传入了 myText
,所以这个 value 就是 true。
<script>
export default {
props: {
modelValue: String,
modelModifiers: {
default: () => ({}),
},
},
emits: ["update:modelValue"],
created() {
console.log(this.modelModifiers); // { capitalize: true }
},
};
</script>
<template>
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
Now that we have our prop set up, we can check the modelModifiers
object keys and write a handler to change the emitted value. In the code below we will capitalize the string whenever the <input />
element fires an input
event.
//...
methods: {
emitValue(e) {
let value = e.target.value
if (this.modelModifiers.capitalize) { // check keys
value = value.charAt(0).toUpperCase() + value.slice(1) // handler
}
this.$emit('update:modelValue', value)
}
}
// ...
Example - v-model
bindings with both argument and modifiers
For v-model
bindings with both argument and modifiers, the generated prop name will be arg + "Modifiers"
.
<MyComponent v-model:title.capitalize="myText"></MyComponent>
注意我们的 prop name 是 titleModifiers
。
export default {
props: ["title", "titleModifiers"],
emits: ["update:title"],
created() {
console.log(this.titleModifiers); // { capitalize: true }
},
};