Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "emits" component option #16

Merged
merged 5 commits into from
Apr 15, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions active-rfcs/0000-emits-option.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
- 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

```javascript
const Comp = {
emits: {
submit: payload => {
// validate payload by returning a boolean
}
},

created() {
this.$emit('submit', {
/* payload */
})
}
}
```

# Motivation

- **Documentation:** Similar to `props`, explicit `emits` declaration 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 `emits` option can be used to provide type inference so that `this.$emit` and `setupContext.emit` calls can be typed.

- **IDE Support:** IDEs can leverage the `emits` option to provide auto-completion when using `v-on` listeners on a component.

- **Listener Fallthrough Control:** With the proposed attribute fallthrough changes, `v-on` listeners on components will fallthrough as native listeners by default. `emits` provides 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:

```javascript
{
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.

```javascript
{
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](https://github.com/vuejs/rfcs/blob/amend-optional-props/active-rfcs/0000-attr-fallthrough.md) proposed in [#154](https://github.com/vuejs/rfcs/pull/154) now applies automatic fallthrough for `v-on` listeners used on a component:

```html
<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 exampe, 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:

```ts
const Foo = defineComponent({
emits: {
submit: (payload: { email: string; password: string }) => {
// perform runtime validation
}
},

methods: {
onSubmit() {
this.$emit('submit', {
email: 'foo@bar.com',
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 `props` and `emits` options 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:

1. Provide a codemod that automatically scans all instances of `$emit` calls in a component and generate the `emits` option.

2. (Opt-in) Emit a runtime warning when an emitted event isn't explicitly declared using the option.