From 1ada66158ab5f23383c98c3c9b70192921769249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20G=C5=82odek?= Date: Wed, 27 Feb 2019 14:06:00 +0100 Subject: [PATCH 1/5] Create 0000-events-option.md --- active-rfcs/0000-events-option.md | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 active-rfcs/0000-events-option.md diff --git a/active-rfcs/0000-events-option.md b/active-rfcs/0000-events-option.md new file mode 100644 index 00000000..9f6b9257 --- /dev/null +++ b/active-rfcs/0000-events-option.md @@ -0,0 +1,111 @@ +- 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 +{ + + events: { + submit: { + email: String, + password: String + } + }, + + data() { + return { + email: '', + password: '' + }; + }, + + methods: { + submit() { + this.$emit('submit', { + email: this.email, + password: this.password + }); + } + } + +} +``` + +# Motivation + +Now, if the developer uses a component, he can easily check what props can be passed to the component. But unfortunatelly, he can't easily check what events he can subscribe to. With this feature, both developer and IDE will know what events are emitted by the component. Code editors will be able to add code completions when developer starts typing v-on: or @ in a component template. + +# Detailed design + +There should be an optional component option named `events`. +Like props, there should be more than one allowed form: + +## Array<string> + +Array containing events names (in camel case): + +```javascript +{ + events: [ + 'eventA', + 'eventB' + } +} +``` + +## Object + +Object with event name (in camel case) as a key and type constructor of the event argument as a value (like props): + +```javascript +{ + events: { + eventA: Object, + eventB: String + } +} +``` + +It's common strategy to pass the event argument as object with many properties, so it should be also possible (and recommended) to do so: + +```javascript +{ + events: { + eventA: { + a: Number, + b: Number + } + } +} +``` + +If the event doesn't pass any argument, the value should be null: + +```javascript +{ + events: { + eventA: null + } +} +``` + +# Drawbacks + +There may be inconsistency if some developers will use `events` option and others won't. + +# Alternatives + +Two things should be discussed: +- What `event` option structure should look like? +- Should event emitting be validated (both event names and types declared in `events` option)? + +# Adoption strategy + +If the event emitting isn't validated, adding the `events` option won't require any change of the library. It should be mentioned in the official Vue Guide and API. Then code editors (e.g. Visual Studio Code, WebStorm, PhpStorm) should add code completions to the events. From 1ea6bef8f940391ea7c67ffe63ef50f8a5183e2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20G=C5=82odek?= Date: Mon, 8 Apr 2019 23:25:37 +0200 Subject: [PATCH 2/5] Update 0000-events-option.md --- active-rfcs/0000-events-option.md | 32 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/active-rfcs/0000-events-option.md b/active-rfcs/0000-events-option.md index 9f6b9257..278875f6 100644 --- a/active-rfcs/0000-events-option.md +++ b/active-rfcs/0000-events-option.md @@ -12,11 +12,8 @@ Make it explicit what events are emitted by the component. ```javascript { - events: { - submit: { - email: String, - password: String - } + emits: { + submit: Object }, data() { @@ -44,7 +41,7 @@ Now, if the developer uses a component, he can easily check what props can be pa # Detailed design -There should be an optional component option named `events`. +There should be an optional component option named `emits`. Like props, there should be more than one allowed form: ## Array<string> @@ -53,7 +50,7 @@ Array containing events names (in camel case): ```javascript { - events: [ + emits: [ 'eventA', 'eventB' } @@ -66,21 +63,20 @@ Object with event name (in camel case) as a key and type constructor of the even ```javascript { - events: { + emits: { eventA: Object, - eventB: String + eventB: [String, Number] } } ``` -It's common strategy to pass the event argument as object with many properties, so it should be also possible (and recommended) to do so: +If you need more complex validation, you can use `validator` method: ```javascript { - events: { + emits: { eventA: { - a: Number, - b: Number + validator: (value) => ['value-a', 'value-b'].includes(value) } } } @@ -90,7 +86,7 @@ If the event doesn't pass any argument, the value should be null: ```javascript { - events: { + emits: { eventA: null } } @@ -98,14 +94,14 @@ If the event doesn't pass any argument, the value should be null: # Drawbacks -There may be inconsistency if some developers will use `events` option and others won't. +There may be inconsistency if some developers will use `emits` option and others won't. # Alternatives Two things should be discussed: -- What `event` option structure should look like? -- Should event emitting be validated (both event names and types declared in `events` option)? +- What `emits` option structure should look like? +- Should event emitting be validated (both event names and types declared in `emits` option)? # Adoption strategy -If the event emitting isn't validated, adding the `events` option won't require any change of the library. It should be mentioned in the official Vue Guide and API. Then code editors (e.g. Visual Studio Code, WebStorm, PhpStorm) should add code completions to the events. +If the event emitting isn't validated, adding the `emits` option won't require any change of the library. It should be mentioned in the official Vue Guide and API. Then code editors (e.g. Visual Studio Code, WebStorm, PhpStorm) should add code completions to the events. From 0bc4485986839e78f9d717da08c8f1e8eb7578bd Mon Sep 17 00:00:00 2001 From: Guillaume Chau Date: Mon, 8 Apr 2019 23:28:48 +0200 Subject: [PATCH 3/5] Rename 0000-events-option.md to 0000-emits-option.md --- active-rfcs/{0000-events-option.md => 0000-emits-option.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename active-rfcs/{0000-events-option.md => 0000-emits-option.md} (100%) diff --git a/active-rfcs/0000-events-option.md b/active-rfcs/0000-emits-option.md similarity index 100% rename from active-rfcs/0000-events-option.md rename to active-rfcs/0000-emits-option.md From ac353c524c18a7b65d52c62353344d57925a1e69 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 3 Apr 2020 18:51:53 -0400 Subject: [PATCH 4/5] revise emits proposal --- active-rfcs/0000-emits-option.md | 120 ++++++++++++++++++------------- 1 file changed, 69 insertions(+), 51 deletions(-) diff --git a/active-rfcs/0000-emits-option.md b/active-rfcs/0000-emits-option.md index 278875f6..086682b5 100644 --- a/active-rfcs/0000-emits-option.md +++ b/active-rfcs/0000-emits-option.md @@ -10,43 +10,38 @@ Make it explicit what events are emitted by the component. # Basic example ```javascript -{ - +const Comp = { emits: { - submit: Object - }, - - data() { - return { - email: '', - password: '' - }; - }, - - methods: { - submit() { - this.$emit('submit', { - email: this.email, - password: this.password - }); + submit: payload => { + // validate payload by returning a boolean } + }, + + created() { + this.$emit('submit', { /* payload */ }) } - } ``` # Motivation -Now, if the developer uses a component, he can easily check what props can be passed to the component. But unfortunatelly, he can't easily check what events he can subscribe to. With this feature, both developer and IDE will know what events are emitted by the component. Code editors will be able to add code completions when developer starts typing v-on: or @ in a component template. +- **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 -There should be an optional component option named `emits`. -Like props, there should be more than one allowed form: +A new optional component option named `emits` is introduced. -## Array<string> +## Array Syntax -Array containing events names (in camel case): +For simple use cases, the option value can be an Array containing string events names: ```javascript { @@ -57,51 +52,74 @@ Array containing events names (in camel case): } ``` -## Object +## Object Syntax -Object with event name (in camel case) as a key and type constructor of the event argument as a value (like props): +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: { - eventA: Object, - eventB: [String, Number] + // 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 -If you need more complex validation, you can use `validator` method: +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: -```javascript -{ - emits: { - eventA: { - validator: (value) => ['value-a', 'value-b'].includes(value) - } - } -} +``` html + ``` -If the event doesn't pass any argument, the value should be null: +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`. -```javascript -{ +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: { - eventA: null + 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 - -There may be inconsistency if some developers will use `emits` option and others won't. +# Adoption strategy -# Alternatives +The introduction of the `emits` option should not break any existing usage of `$emit`. -Two things should be discussed: -- What `emits` option structure should look like? -- Should event emitting be validated (both event names and types declared in `emits` option)? +However, with the fallthrough behavior change, it would be ideal to always declare emitted events. We can: -# Adoption strategy +1. Provide a codemod that automatically scans all instances of `$emit` calls in a component and generate the `emits` option. -If the event emitting isn't validated, adding the `emits` option won't require any change of the library. It should be mentioned in the official Vue Guide and API. Then code editors (e.g. Visual Studio Code, WebStorm, PhpStorm) should add code completions to the events. +2. Emit a runtime warning when an emitted event isn't explicitly declared using the option. From 308c7ed010818fb731a20c2e0ca467de3b1e395b Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 3 Apr 2020 18:57:41 -0400 Subject: [PATCH 5/5] add drawbacks section --- active-rfcs/0000-emits-option.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/active-rfcs/0000-emits-option.md b/active-rfcs/0000-emits-option.md index 086682b5..4eea6677 100644 --- a/active-rfcs/0000-emits-option.md +++ b/active-rfcs/0000-emits-option.md @@ -18,7 +18,9 @@ const Comp = { }, created() { - this.$emit('submit', { /* payload */ }) + this.$emit('submit', { + /* payload */ + }) } } ``` @@ -75,11 +77,12 @@ Or it can be an object with event names as its keys. The value of each property } } ``` + ## 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 +```html ``` @@ -93,10 +96,10 @@ Event listeners declared by `emits` are also excluded from `this.$attrs` of the The Object validator syntax was picked with TypeScript type inference in mind. The validator type signature can be used to type `$emit` calls: -``` ts +```ts const Foo = defineComponent({ emits: { - submit: (payload: { email: string, password: string }) => { + submit: (payload: { email: string; password: string }) => { // perform runtime validation } }, @@ -114,6 +117,12 @@ const Foo = defineComponent({ }) ``` +# 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`. @@ -122,4 +131,4 @@ However, with the fallthrough behavior change, it would be ideal to always decla 1. Provide a codemod that automatically scans all instances of `$emit` calls in a component and generate the `emits` option. -2. Emit a runtime warning when an emitted event isn't explicitly declared using the option. +2. (Opt-in) Emit a runtime warning when an emitted event isn't explicitly declared using the option.