-
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 16 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,158 @@ | ||
--- | ||
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 have 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: | ||
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.
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. Fixed. 👍 |
||
|
||
```js | ||
this.$emit('myEvent') | ||
``` | ||
|
||
Listening to the kebab-cased version will have no effect: | ||
|
||
```html | ||
<my-component v-on:my-event="doSomething"></my-component> | ||
``` | ||
|
||
The reason for this is that 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. | ||
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.
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 like it! Updated. 🙂 |
||
|
||
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)" | ||
> | ||
` | ||
}) | ||
``` | ||
|
||
<p class="tip">Note that you still have to declare the `checked` prop in `props`.</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. | ||
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.
A missing coma. 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. Fixed. 👍 |
||
|
||
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 has the effect of adding `v-on` update listeners for not only `title`, but also any other properties on the `doc` object. | ||
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. Instead of
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, this seems redundant to me since we just established the context in the previous sentence. @sdras What do you think? 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. Does code snippets between paragraphs break context? For large code snippets, it happens. I tend to go back to last line before continuing to next paragraph. For one line snippet, I am not sure. Moreover, the amount code breaking reading context may vary from person to person. 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 prefer "This" here. One line of code in between is not enough to repeat the 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. @znck Yeah, I agree with the principle in general, but in this particular case I think it will be better for most people to leave it out. 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, I read it through and it seemed redundant to change it, it's pretty clear to me what this refers to. But that's a good thing to look out for! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
--- | ||
title: Dynamic & Async Components | ||
type: guide | ||
order: 105 | ||
--- | ||
|
||
> This page assumes you've already read the [Components Basics](components.html). Read that first if you are new to components. | ||
|
||
## `keep-alive` with Dynamic Components | ||
|
||
Earlier, we used the `is` attribute to switch between components in a tabbed interface: | ||
|
||
```html | ||
<component v-bind:is="currentTabComponent"></component> | ||
``` | ||
|
||
When switching between these components though, you'll sometimes want to maintain their state or avoid re-rendering for performance reasons. In these cases, you can wrap a dynamic component with a `<keep-alive>` element: | ||
|
||
``` html | ||
<!-- Inactive components will be cached! --> | ||
<keep-alive> | ||
<component v-bind:is="currentTabComponent"></component> | ||
</keep-alive> | ||
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. This, and the API don't fully show off the benefits of 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 agree and think that's a great idea! 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. Couldn't help myself and added it. 😅 Your idea was too good! |
||
``` | ||
|
||
<p class="tip">Note that `<keep-alive>` requires the components being switched between to all have names, either using the `name` option on a component, or through local/global registration.</p> | ||
|
||
Check out more details on `<keep-alive>` in the [API reference](../api/#keep-alive). | ||
|
||
## Async Components | ||
|
||
In large applications, we may need to divide the app into smaller chunks and only load a component from the server when it's actually needed. To make that easier, Vue allows you to define your component as a factory function that asynchronously resolves your component definition. Vue will only trigger the factory function when the component actually needs to be rendered and will cache the result for future re-renders. For 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. We can remove
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 like it! Removed. 🙂 |
||
|
||
``` js | ||
Vue.component('async-example', function (resolve, reject) { | ||
setTimeout(function () { | ||
// Pass the component definition to the resolve callback | ||
resolve({ | ||
template: '<div>I am async!</div>' | ||
}) | ||
}, 1000) | ||
}) | ||
``` | ||
|
||
As you can see, the factory function receives a `resolve` callback, which should be called when you have retrieved your component definition from the server. You can also call `reject(reason)` to indicate the load has failed. The `setTimeout` here is for demonstration; how to retrieve the component is up to you. One recommended approach is to use async components together with [Webpack's code-splitting feature](https://webpack.js.org/guides/code-splitting/): | ||
|
||
``` js | ||
Vue.component('async-webpack-example', function (resolve) { | ||
// This special require syntax will instruct Webpack to | ||
// automatically split your built code into bundles which | ||
// are loaded over Ajax requests. | ||
require(['./my-async-component'], resolve) | ||
}) | ||
``` | ||
|
||
You can also return a `Promise` in the factory function, so with Webpack 2 and ES2015 syntax you can do: | ||
|
||
``` js | ||
Vue.component( | ||
'async-webpack-example', | ||
// The `import` function returns a Promise. | ||
() => import('./my-async-component') | ||
) | ||
``` | ||
|
||
When using [local registration](components.html#Local-Registration), you can also directly provide a function that returns a `Promise`: | ||
|
||
``` js | ||
new Vue({ | ||
// ... | ||
components: { | ||
'my-component': () => import('./my-async-component') | ||
} | ||
}) | ||
``` | ||
|
||
<p class="tip">If you're a <strong>Browserify</strong> user that would like to use async components, its creator has unfortunately [made it clear](https://github.com/substack/node-browserify/issues/58#issuecomment-21978224) that async loading "is not something that Browserify will ever support." Officially, at least. The Browserify community has found [some workarounds](https://github.com/vuejs/vuejs.org/issues/620), which may be helpful for existing and complex applications. For all other scenarios, we recommend using Webpack for built-in, first-class async support.</p> | ||
|
||
### Handling Loading State | ||
|
||
> New in 2.3.0+ | ||
|
||
The async component factory can also return an object of the following format: | ||
|
||
``` js | ||
const AsyncCompontent = () => ({ | ||
// The component to load (should be a Promise) | ||
component: import('./MyComponent.vue'), | ||
// A component to use while the async component is loading | ||
loading: LoadingComponent, | ||
// A component to use if the load fails | ||
error: ErrorComponent, | ||
// Delay before showing the loading component. Default: 200ms. | ||
delay: 200, | ||
// The error component will be displayed if a timeout is | ||
// provided and exceeded. Default: Infinity. | ||
timeout: 3000 | ||
}) | ||
``` | ||
|
||
> Note that you must use [Vue Router](https://github.com/vuejs/vue-router) 2.4.0+ if you wish to use the above syntax for route components. |
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!