recorded decision · public · no signup
Summary
What was chosen
- A new optional `emits` component option is introduced to explicitly declare events emitted by a component.adr ↗
- The `emits` option can be an array of event names or an object with validator functions.adr ↗
- A Babel plugin can strip runtime validation from production builds to avoid bundle size bloat.adr ↗
Constraints
- The `emits` option introduction must not break existing `$emit` usage to maintain backwards compatibility.adr ↗
Consequences
- IDEs can use the `emits` option to provide auto-completion for `v-on` listeners.adr ↗
- The `emits` option allows for better TypeScript type inference for `$emit` calls.adr ↗
- The `emits` option requires extra effort for components that emit custom events.adr ↗
- The `emits` option provides self-documenting code, improving clarity for other developers.adr ↗
- The `emits` option enables runtime validation of emitted event payloads.adr ↗
- Declaring an event with the `emits` option prevents it from falling through as a native listener.adr ↗
The recorded why
- Start Date: 2019-02-27
- Target Major Version: 2.x & 3.x
- Reference Issues: N/A
- Implementation PR:
Summary
Make it explicit what events are emitted by the component.
Basic example
const Comp = {
emits: {
submit: payload => {
// validate payload by returning a boolean
}
},
created() {
this.$emit('submit', {
/* payload */
})
}
}
Motivation
-
Documentation: Similar to
props, explicitemitsdeclaration serves as self-documenting code. This can be useful for other developers to instantly understand what events the component is supposed to emit. -
Runtime Validation: The option also offers a way to perform runtime validation of emitted event payloads.
-
Type Inference: The
emitsoption can be used to provide type inference so thatthis.$emitandsetupContext.emitcalls can be typed. -
IDE Support: IDEs can leverage the
emitsoption to provide auto-completion when usingv-onlisteners on a component. -
Listener Fallthrough Control: With the proposed attribute fallthrough changes,
v-onlisteners on components will fallthrough as native listeners by default.emitsprovides a way to declare events as component-only to avoid unnecessary registration of native listeners.
Detailed design
A new optional component option named emits is introduced.
§ Array Syntax
For simple use cases, the option value can be an Array containing string events names:
{
emits: [
'eventA',
'eventB'
}
}
§ Object Syntax
Or it can be an object with event names as its keys. The value of each property can either be null or a validator function. The validation function will receive the additional arguments passed to the $emit call. For example, if this.$emit('foo', 1, 2) is called, the corresponding validator for foo will receive the arguments 1, 2. The validator function should return a boolean to indicate whether the event arguments are valid.
{
emits: {
// no validation
click: null,
// with validation
//
submit: payload => {
if (payload.email && payload.password) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
}
§ Fallthrough Control
The new Attribute Fallthrough Behavior proposed in #154 now applies automatic fallthrough for v-on listeners used on a component:
<Foo @click="onClick" />
We are not making emits required for click to be trigger-able by component emitted events for backwards compatibility. Therefore in the above example, without the emits option, the listener can be triggered by both a native click event on Foo's root element, or a custom click event emitted by Foo.
If, however, click is declared as a custom event by using the emits option, it will then only be triggered by custom events and will no longer fallthrough as a native listener.
Event listeners declared by emits are also excluded from this.$attrs of the component.
§ Type Inference
The Object validator syntax was picked with TypeScript type inference in mind. The validator type signature can be used to type $emit calls:
const Foo = defineComponent({
emits: {
submit: (payload: { email: string; password: string }) => {
// perform runtime validation
}
},
methods: {
onSubmit() {
this.$emit('submit', {
email: '[email protected]',
password: 123 // Type error!
})
this.$emit('non-declared-event') // Type error!
}
}
})
Drawbacks
-
The option requires some extra effort for all components that emit custom events. However, it is technically optional, and the benefits should outweigh the extra effort needed.
-
Runtime validations should only be performed in dev mode but can potentially bloat production bundle size. Props validators have the same issue. Both can be solved with a Babel plugin that transforms
propsandemitsoptions to the Array format in production builds. This way the dev only code is stripped but the runtime behavior will stay consistent.
Adoption strategy
The introduction of the emits option should not break any existing usage of $emit.
However, with the fallthrough behavior change, it would be ideal to always declare emitted events. We can:
-
Provide a codemod that automatically scans all instances of
$emitcalls in a component and generate theemitsoption. -
(Opt-in) Emit a runtime warning when an emitted event isn't explicitly declared using the option.