|
| 1 | +- Start Date: 2019-02-27 |
| 2 | +- Target Major Version: 2.x & 3.x |
| 3 | +- Reference Issues: N/A |
| 4 | +- Implementation PR: |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +Make it explicit what events are emitted by the component. |
| 9 | + |
| 10 | +# Basic example |
| 11 | + |
| 12 | +```javascript |
| 13 | +const Comp = { |
| 14 | + emits: { |
| 15 | + submit: payload => { |
| 16 | + // validate payload by returning a boolean |
| 17 | + } |
| 18 | + }, |
| 19 | + |
| 20 | + created() { |
| 21 | + this.$emit('submit', { |
| 22 | + /* payload */ |
| 23 | + }) |
| 24 | + } |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +# Motivation |
| 29 | + |
| 30 | +- **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. |
| 31 | + |
| 32 | +- **Runtime Validation:** The option also offers a way to perform runtime validation of emitted event payloads. |
| 33 | + |
| 34 | +- **Type Inference:** The `emits` option can be used to provide type inference so that `this.$emit` and `setupContext.emit` calls can be typed. |
| 35 | + |
| 36 | +- **IDE Support:** IDEs can leverage the `emits` option to provide auto-completion when using `v-on` listeners on a component. |
| 37 | + |
| 38 | +- **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. |
| 39 | + |
| 40 | +# Detailed design |
| 41 | + |
| 42 | +A new optional component option named `emits` is introduced. |
| 43 | + |
| 44 | +## Array Syntax |
| 45 | + |
| 46 | +For simple use cases, the option value can be an Array containing string events names: |
| 47 | + |
| 48 | +```javascript |
| 49 | +{ |
| 50 | + emits: [ |
| 51 | + 'eventA', |
| 52 | + 'eventB' |
| 53 | + } |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +## Object Syntax |
| 58 | + |
| 59 | +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. |
| 60 | + |
| 61 | +```javascript |
| 62 | +{ |
| 63 | + emits: { |
| 64 | + // no validation |
| 65 | + click: null, |
| 66 | + |
| 67 | + // with validation |
| 68 | + // |
| 69 | + submit: payload => { |
| 70 | + if (payload.email && payload.password) { |
| 71 | + return true |
| 72 | + } else { |
| 73 | + console.warn(`Invalid submit event payload!`) |
| 74 | + return false |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | +} |
| 79 | +``` |
| 80 | + |
| 81 | +## Fallthrough Control |
| 82 | + |
| 83 | +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: |
| 84 | + |
| 85 | +```html |
| 86 | +<Foo @click="onClick" /> |
| 87 | +``` |
| 88 | + |
| 89 | +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`. |
| 90 | + |
| 91 | +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. |
| 92 | + |
| 93 | +Event listeners declared by `emits` are also excluded from `this.$attrs` of the component. |
| 94 | + |
| 95 | +## Type Inference |
| 96 | + |
| 97 | +The Object validator syntax was picked with TypeScript type inference in mind. The validator type signature can be used to type `$emit` calls: |
| 98 | + |
| 99 | +```ts |
| 100 | +const Foo = defineComponent({ |
| 101 | + emits: { |
| 102 | + submit: (payload: { email: string; password: string }) => { |
| 103 | + // perform runtime validation |
| 104 | + } |
| 105 | + }, |
| 106 | + |
| 107 | + methods: { |
| 108 | + onSubmit() { |
| 109 | + this.$emit('submit', { |
| 110 | + |
| 111 | + password: 123 // Type error! |
| 112 | + }) |
| 113 | + |
| 114 | + this.$emit('non-declared-event') // Type error! |
| 115 | + } |
| 116 | + } |
| 117 | +}) |
| 118 | +``` |
| 119 | + |
| 120 | +# Drawbacks |
| 121 | + |
| 122 | +- 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. |
| 123 | + |
| 124 | +- 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. |
| 125 | + |
| 126 | +# Adoption strategy |
| 127 | + |
| 128 | +The introduction of the `emits` option should not break any existing usage of `$emit`. |
| 129 | + |
| 130 | +However, with the fallthrough behavior change, it would be ideal to always declare emitted events. We can: |
| 131 | + |
| 132 | +1. Provide a codemod that automatically scans all instances of `$emit` calls in a component and generate the `emits` option. |
| 133 | + |
| 134 | +2. (Opt-in) Emit a runtime warning when an emitted event isn't explicitly declared using the option. |
0 commit comments