-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Moving Components Page into its Own Category #1482
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
Changes from all commits
b5cd524
38ae34c
c816498
0a228f5
12a4b9c
7a42b83
65d65c0
81f60f2
e8ab0ec
594b1d0
7905383
e63512c
8389490
3f9d882
c37c9d0
4fae206
c1af324
57ee320
c329098
60e5c25
349247c
4301124
512d8cc
869e4dd
ce9b9e9
40731be
070bfb8
f7ee91c
1db36a0
7901ad1
e3d6f7d
1d90cba
ed09a59
62b6cdf
6ccbbe6
062afe3
7194e29
f858355
59ec1e4
b398f94
ab0b0f5
0d06bd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
--- | ||
title: Custom Events | ||
type: guide | ||
order: 103 | ||
--- | ||
|
||
> This page assumes you've already read the [Components Basics](components.html). Read that first if you are new to components. | ||
|
||
## Event Names | ||
|
||
Unlike components and props, event names don't provide any automatic case transformation. Instead, the name of an emitted event must exactly match the name used to listen to that event. For example, if emitting a camelCased event name: | ||
|
||
```js | ||
this.$emit('myEvent') | ||
``` | ||
|
||
Listening to the kebab-cased version will have no effect: | ||
|
||
```html | ||
<my-component v-on:my-event="doSomething"></my-component> | ||
``` | ||
|
||
Unlike components and props, event names will never be used as variable or property names in JavaScript, so there's no reason to use camelCase or PascalCase. Additionally, `v-on` event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML's case-insensitivity), so `v-on:myEvent` would become `v-on:myevent` -- making `myEvent` impossible to listen to. | ||
|
||
For these reasons, we recommend you **always use kebab-case for event names**. | ||
|
||
## Customizing Component `v-model` | ||
|
||
> New in 2.2.0+ | ||
|
||
By default, `v-model` on a component uses `value` as the prop and `input` as the event, but some input types such as checkboxes and radio buttons may want to use the `value` attribute for a [different purpose](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#Value). Using the `model` option can avoid a conflict in such cases: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The indefinite article "a" is unnecessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to leave this in actually, because while many dialects of English would allow omitting the "a", I think it would sound strange in American English. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. About to say the same. How is "a" unnecessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @znck can correct me if I'm wrong, but I think in Indian English it's more common, and even in American English there will be no article in some contexts. For example, "there was conflict between two countries." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, in American English, omitting the a in this case would sound strange. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I often (read: almost always) rely on Grammarly for this, and it seems to dislike omitting the article in @chrisvfritz's example: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @phanan 👍 for Grammarly - great tool! I do disagree with it sometimes though. 😄 I think this is a phrase people use on the news quite often. |
||
|
||
```js | ||
Vue.component('base-checkbox', { | ||
model: { | ||
prop: 'checked', | ||
event: 'change' | ||
}, | ||
props: { | ||
checked: Boolean | ||
}, | ||
template: ` | ||
<input | ||
type="checkbox" | ||
v-bind:checked="checked" | ||
v-on:change="$emit('change', $event.target.value)" | ||
> | ||
` | ||
}) | ||
``` | ||
|
||
Now when using `v-model` on this component: | ||
|
||
```js | ||
<base-checkbox v-model="lovingVue"></base-checkbox> | ||
``` | ||
|
||
the value of `lovingVue` will be passed to the `checked` prop. The `lovingVue` property will then be updated when `<base-checkbox>` emits a `change` event with a new value. | ||
|
||
<p class="tip">Note that you still have to declare the <code>checked</code> prop in component's <code>props</code> option.</p> | ||
|
||
## Binding Native Events to Components | ||
|
||
There may be times when you want to listen directly to a native event on the root element of a component. In these cases, you can use the `.native` modifier for `v-on`: | ||
|
||
```html | ||
<base-input v-on:focus.native="onFocus"></base-input> | ||
``` | ||
|
||
This can be useful sometimes, but it's not a good idea when you're trying to listen on a very specific element, like an `<input>`. For example, the `<base-input>` component above might refactor so that the root element is actually a `<label>` element: | ||
|
||
```html | ||
<label> | ||
{{ label }} | ||
<input | ||
v-bind="$attrs" | ||
v-bind:value="value" | ||
v-on:input="$emit('input', $event.target.value)" | ||
> | ||
</label> | ||
``` | ||
|
||
In that case, the `.native` listener in the parent would silently break. There would be no errors, but the `onFocus` handler wouldn't be called when we expected it to. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Can we simplify this statement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, nothing is coming to mind, but I'm definitely open to suggestions! |
||
|
||
To solve this problem, Vue provides a `$listeners` property containing an object of listeners being used on the component. For example: | ||
|
||
```js | ||
{ | ||
focus: function (event) { /* ... */ } | ||
input: function (value) { /* ... */ }, | ||
} | ||
``` | ||
|
||
Using the `$listeners` property, you can forward all event listeners on the component to a specific child element with `v-on="$listeners"`. For elements like `<input>`, that you also want to work with `v-model`, it's often useful to create a new computed property for listeners, like `inputListeners` below: | ||
|
||
```js | ||
Vue.component('base-input', { | ||
inheritAttrs: false, | ||
props: ['label', 'value'], | ||
computed: { | ||
inputListeners: function () { | ||
var vm = this | ||
// `Object.assign` merges objects together to form a new object | ||
return Object.assign({}, | ||
// We add all the listeners from the parent | ||
this.$listeners, | ||
// Then we can add custom listeners or override the | ||
// behavior of some listeners. | ||
{ | ||
// This ensures that the component works with v-model | ||
input: function (event) { | ||
vm.$emit('input', event.target.value) | ||
} | ||
} | ||
) | ||
} | ||
}, | ||
template: ` | ||
<label> | ||
{{ label }} | ||
<input | ||
v-bind="$attrs" | ||
v-bind:value="value" | ||
v-on="inputListeners" | ||
> | ||
</label> | ||
` | ||
}) | ||
``` | ||
|
||
Now the `<base-input>` component is a **fully transparent wrapper**, meaning it can be used exactly like a normal `<input>` element: all the same attributes and listeners will work. | ||
|
||
## `.sync` Modifier | ||
|
||
> New in 2.3.0+ | ||
|
||
In some cases, we may need "two-way binding" for a prop. Unfortunately, true two-way binding can create maintenance issues, because child components can mutate the parent without the source of that mutation being obvious in both the parent and the child. | ||
|
||
That's why instead, we recommend emitting events in the pattern of `update:my-prop-name`. For example, in a hypothetical component with a `title` prop, we could communicate the intent of assigning a new value with: | ||
|
||
```js | ||
this.$emit('update:title', newTitle) | ||
``` | ||
|
||
Then the parent can listen to that event and update a local data property, if it wants to. For example: | ||
|
||
```html | ||
<text-document | ||
v-bind:title="doc.title" | ||
v-on:update:title="doc.title = $event" | ||
></text-document> | ||
``` | ||
|
||
For convenience, we offer a shorthand for this pattern with the `.sync` modifier: | ||
|
||
```html | ||
<text-document v-bind:title.sync="doc.title"></text-document> | ||
``` | ||
|
||
The `.sync` modifier can also be used with `v-bind` when using an object to set multiple props at once: | ||
|
||
```html | ||
<text-document v-bind.sync="doc"></text-document> | ||
``` | ||
|
||
This passes each property in the `doc` object (e.g. `title`) as an individual prop, then adds `v-on` update listeners for each one. | ||
|
||
<p class="tip">Using <code>v-bind.sync</code> with a literal object, such as in <code>v-bind.sync="{ title: doc.title }"</code>, will not work. If you want to include multiple, unrelated data properties in the same <code>v-bind.sync</code>, we recommend creating a computed property that returns an object.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we've talked about this before, are we all ok with transparent wrapper instead of higher-order? I think higher-order is more commonly used so it's possible it will be understood much faster by people learning, but I could go both ways on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this different from an HOC? The definition used by React is:
This is a component rather than a function and it returns raw elements rather than a new component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not necessarily, from the same document just a paragraph lower:
Having worked with both, I'm not sure I'd make such a distinction, but if you think that it's helpful for understanding, I'm all for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please correct me if I'm still missing something, 😅 but I don't think this scenario fits that definition either though. Inside this component, we're wrapping a specific element to create a custom version of it, rather than wrapping or transforming a component. Does that make sense?
(Btw, I do agree with you that we should mention the vocabulary of HOCs when we use that pattern, I'm just not sure this is an example of it.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good!