Vue.js
Vue Instance
The Vue
function can be used to create a Vue instance, which by convention is often named vm
for “ViewModel.” The Vue
function takes an options object.
Upon creation, all properties found in its data
object are registered with the reactivity system. Only the data
properties that existed at instantiation time are reactive.
const data = { a: 1 };
const vm = new Vue({ data });
vm.a === data.a; // => true
vm.a = 2;
data.a // => 2
data.a = 3;
vm.a // => 3
Various built-in Vue
instance properties and methods are prefixed with $
.
vm.$data === data; // => true
Lifecycle Hooks
It’s possible to define lifecycle hooks just as in React. Each hook has this
set to the Vue instance invoking it, so avoid using arrow functions as that overrides it with the parent context. Possible hooks include created
, mounted
, updated
, and destroyed
.
new Vue({
data: { a: 1 },
created() {
console.log(`a is: ${this.a}`);
},
});
// => 'a is: 1'
Here’s a diagram detailing a Vue instance’s lifecycle:
Templates
Vue’s templates remind me a bit of Angular’s. A common misconception is that this is just “plain HTML,” often mentioned as a counterpoint to React’s JSX. However, Vue’s templates—although possible to define as HTML—are nonetheless compiled to JavaScript as Virtual DOM render functions, similar to JSX.
Text interpolation can be accomplished through Mustache-style curly braces. These interpolations will update whenever the value being interpolated changes. The v-once
directive can be used to suppress that behavior.
<span>Reactive message: {{ message }}</span>
<span v-once>This will not update: {{ message }}</span>
The above interpolations escape HTML, but raw HTML can be inserted via the v-html
directive, which takes as argument the property containing the HTML, and causes the contents of the tag it is placed on to take on that HTML content.
<div v-html="rawHTMLProperty"></div>
Mustaches cannot be placed within HTML attributes; instead, the v-bind
directive should be used to “bind” a value to an attribute. Boolean attributes such as disabled
are configured to remove themselves if the value is falsy.
<!-- `id` attribute set to the value of `idProperty`. -->
<div v-bind:id="idProperty"></div>
<!-- if `isButtonDisabled` is falsy, remove `disabled` attribute. -->
<button v-bind:disabled="isButtonDisabled">Button</button>
Mustaches and attributes can contain arbitrary JavaScript expressions, but like JSX, each may contain a single expression.
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
<div v-bind:id="'list-' + id"></div>
Directives
Directives are attributes with a v-
prefix. Directives may take arguments, denoted by a colon :
after the directive name.
<!-- argument is `href` -->
<a v-bind:href="url"></a>
Modifiers are denoted by a dot .
prefix, and may be stacked.
<!-- modifier is `prevent` -->
<form v-on:submit.prevent="onSubmit"></form>
The shorthand for v-bind
is the colon :
by itself.
<a v-bind:href="url"></a>
<a :href="url"></a>
The shorthand for v-on
is the at symbol @
.
<a v-on:click="clicked"></a>
<a @click="clicked"></a>
Class and Style Bindings
The v-bind:class
directive can accept an object where keys are class names that will be part of the final class
attribute if the corresponding value is truthy. The object can be specified inline or as a reference to a property. A computed property may be most natural to use for this, since dependencies are automatically registered.
A separate, direct class
attribute may also be specified and will be part of the resulting class
attribute unconditionally.
<div v-bind:class="{ active: isActive }"></div>
<div v-bind:class="someObject"></div>
The v-bind:class
directive can also accept an array of class names. Going further, objects may be embedded within an array of classes to mix conditional and unconditional classes.
<div v-bind:calss"[unconditionalClass, { conditionalClass: isActive }]"></div>
When a class
attribute is used on a custom component, it ends up being applied to that component’s root element, appended if that element itself contains a class
attribute.
Much like v-bind:class
, v-bind:style
also accepts an object of style properties, referred to as a “style object”. It also accepts an array of style objects that are merged together with later conflicting styles overriding earlier ones.
Vue automatically applies vendor prefixes to CSS property names that require them.
Conditional Rendering
The v-if
directive can be used to conditionally render an element. An immediately-following, sibling element can optionally be provided with a corresponding v-else
directive, with the expected effect.
<div v-if="isVisible">Visible</div>
<div v-else></div>
The <template>
element can be used as an invisible wrapper to group multiple same-level elements in order to, for example, toggle all of their visibility at once.
There is also a v-else-if
directive that has the expected effect.
The v-show
directive can be used to conditionally hide an element. Whereas v-if
affects whether or not the element is rendered at all, v-show
simply toggles the element’s display
property. For this reason, it wouldn’t work on a <template>
element since it has no corresponding element in the output DOM on which to apply the display
property.
Since v-if
affects whether or not the element is rendered at all, if it’s not rendered, it will avoid creating the element and with it everything that that may entail, such as creating event listeners and child components. On the other hand, since v-show
only affects the resulting element’s visibility, the element and everything it contains is fully created.
That means that v-show
has a higher initial render cost but cheaper toggle cost, whereas v-if
has a lower initial render cost (if it’s not rendered) but a higher toggle cost (having to recreate the elements each time).
Prefer to use v-show
for content that is likely to be toggled often, and v-if
otherwise.
List Rendering
The v-for
directive can be used to replicate the element it’s applied to for each element in an array, object, or integer range. It’s possible to replicate multiple elements by wrapping them in the <template>
tag, as with v-if
.
<ul>
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
It’s possible to enumerate the indices of the array by binding a second variable:
<li v-for="(item, index) in items">
The v-for
enumeration syntax may use in
or of
as a delimiter.
The v-for
directive may also iterate over an object’s property values. However, as with array index enumeration, the key can be enumerated as well by binding a second variable. In fact, an index can also be bound by providing yet another third variable.
<div v-for="value in object">{{ value }}</div>
<div v-for="(value, key) in object">{{ key }}: {{ value }}</div>
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
The v-for
directive can also iterate over a range by supplying an integer instead of an array or object.
<div>
<span v-for="n in 10">{{ n }}</span>
</div>
When an element contains both v-for
and v-if
, the v-for
has higher precedence and is evaluated before the v-if
, meaning that each replicated element will evaluate v-if
separately.
The v-for
directive can be applied to custom components, but data must be passed explicitly via v-bind
for example, since each component has its own isolated scope. It was Vue’s design decision to be explicit about the interface rather than implicitly—perhaps inadvertently—tightly couple the component with its parent.
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id">
</my-component>
Element Keys
Similar DOM sub-trees can be differentiated with a key
attribute, as in React. This ensures that the Virtual DOM diff algorithm wont inadvertently reuse elements that it shouldn’t reuse.
It’s considered good practice to use a key
in conjunction with a v-for
directive to ensure optimal efficiency and avoid surprising behavior.
Computed Properties
A computed property can be defined for complex expressions, often based on instance properties.
For example, the following should be factored out to a computed property:
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
Defining a computed property has the effect of defining a getter function for a property with the provided name.
const vm = Vue({
el: '#example',
data: { message: 'Hello' },
computed: {
reversedMessage() {
return this.message.split('').reverse().join('');
},
},
})
Since this defines a property, it can be accessed like any other:
<div id="example">
{{ reversedMessage }}
</div>
Vue is aware of a computed property’s data dependencies, so that when those dependencies are updated, so are any bindings that themselves depend on the computed property.
Unlike methods, computed properties are cached based on their dependencies, so that they are only re-evaluated if any of the dependencies have changed, otherwise it serves the cached value.
For that same reason, including any global side-effect code such as Date.now()
may not have the intended effect, because the value will not change if only that expression changed, since it’s not a reactive dependency. Such code could be better expressed as a method.
Watchers may appear to be equivalent to computed properties. For example, the following intends to maintain a fullName
property updated whenever either the firstName
or the lastName
changes.
var vm = new Vue({
data: {
firstName: 'John',
lastName: 'Doe',
fullName: 'John Doe'
},
watch: {
firstName(val) {
this.fullName = val + ' ' + this.lastName
},
lastName(val) {
this.fullName = this.firstName + ' ' + val
},
},
});
However, the above is much more verbose and imperative compared to the declarative, computed property implementation. With a computed property implementation, Vue already knows to only recompute the value when the dependencies change, and the dependencies are automatically detected. This makes the code less error-prone and more flexible, such as in the event that the dependencies change.
var vm = new Vue({
data: {
firstName: 'John',
lastName: 'Doe'
},
computed: {
fullName() {
return this.firstName + ' ' + this.lastName
},
},
})
A setter can also be provided for a computed property. For example, the setter below will ensure that if the value of the computed property is set, the underlying dependencies are updated accordingly.
var vm = new Vue({
data: {
firstName: 'John',
lastName: 'Doe'
},
computed: {
get() {
return this.firstName + ' ' + this.lastName;
},
set(newValue) {
const names = newValue.split(' ');
this.firstName = names[0];
this.lastName = names[names.length - 1];
},
},
})
Watchers
Watchers are a more general construct compared to computed properties. A function can be registered to “watch” a given property, and execute when it is changed. This is accomplished by registering a function for a given property which accepts the new value of that property.
Watchers are usually used in asynchronous or expensive computational contexts.
const vm = new Vue({
data: { someProperty: 1 },
watch: {
someProperty(newValue) {
…
}
}
})
Reactivity Caveats
Vue wraps observed arrays’ mutation methods so that they trigger view updates. In particular, the wrapped methods are:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Since objects are also observed, it’s also possible to simply replace an array property.
Vue cannot detect direct element setting via the indexing operator.
// wrong
vm.items[index] = value;
// correct
Vue.set(vm.items, index, value);
Vue also cannot detect the direct modification of the array length.
// wrong
vm.items.length = newLength;
// correct
vm.items.splice(newLength);
Vue also cannot detect property addition or deletion, which is why all data properties must be defined upfront, even if to empty values. This also is why Vue cannot allow dynamically adding new root-level reactive properties. This is possible to do on existing objects via Vue.set()
:
Vue.set(vm.someObject, 'newProperty', someValue);
// Equivalent (in instance):
this.$set(this.someObject, 'newProperty', someValue);
When doing mass assignment via Object.assign
, do so in an immutable fashion as in Redux, replacing the original property, instead of directly mutating the object.
// wrong
Object.assign(this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
});
// correct
this.userProfile = Object.assign({}, this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
});
When wanting to show filtered or sorted results, it’s best to create a computed property with those filtered or sorted items. If this would end up being too expensive, it’s also possible to just define a method that does this.
Event Handling
The v-on
directive can be used to listen to DOM events and register methods to be invoked in response to them. Invoked methods are passed the event object. The directive can take as argument the event to listen for.
Any event listeners registered with v-on
are automatically removed when the ViewModel is destroyed.
The directive can take modifiers which affect the event, such as .stop
to invoke event.stopPropagation()
. Note that the code is generated in the order specified by the modifiers. Possible modifiers include:
Modifier | Description |
---|---|
.stop |
e.stopPropagation() |
.prevent |
e.preventDefault() |
.capture |
Handle before inner element |
.self |
Only trigger if is event.target |
.once |
Trigger at most once |
The v-on
directive also has modifiers for filtering for specific keycodes when listening for keyboard events by defining modifiers with aliases for common keycodes, such as .enter
.
<!-- Explicit write enter's keycode -->
<input v-on:keyup.13="submit"></input>
<!-- Or use the alias -->
<input v-on:keyup.enter="submit"></input>
It’s possible to define new key modifier aliases using the Vue.config.keyCodes
object.
Vue.config.keycodes.f1 = 11;
Existing key modifiers are:
.enter
.tab
.delete
(also captures backspace).esc
.space
.up
.down
.left
.right
There are also modifiers for key modifiers:
.ctrl
.alt
.shift
.meta
There are also modifiers for mouse buttons:
.left
.right
.middle
<!-- Alt + C -->
<input v-on:keyup.alt.67="clear">
<!-- Ctrl + Click -->
<div v-on:click.ctrl="doSomething">Do something</div>
Custom Events
Every Vue instance implements an events interface so that it can listen to an event via $on(eventName)
and trigger an event via $emit(eventName)
.
A parent component can listen to events emitted from a child via the v-on
directive on that child component.
For example, the below example shows that the child component emits an event whenever it is clicked, which the parent component listens on to maintain a total count.
<div id="counter-event-example">
<p>{{ total }}</p>
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
data() {
return {
counter: 0,
};
},
methods: {
incrementCounter() {
this.counter += 1;
this.$emit('increment');
},
},
});
new Vue({
el: '#counter-event-example',
data: { total: 0 },
methods: {
incrementTotal() {
this.total += 1;
},
},
});
A parent component can listen for a native event on a child component’s root element by using the .native
modifier for v-on
.
<my-component v-on:click.native="doTheThing"></my-component>
It is possible to declare a prop to be a two-way binding by using the .sync
modifier on v-bind
. This actually has the effect of adding a v-on
directive listening for the update
event on the specified property, in which case it updates the property.
<!-- Declare prop `foo` to be a two-way binding to parent's `bar` property. -->
<comp v-bin:foo.sync="bar"></comp>
<!-- Expands to this.
An explicit listener for the update event affecting the `foo` prop,
which has the effect of setting parent's `bar` property to the new
given value. -->
<comp v-bin:foo="bar" v-on:update:foo="(val) => bar = val"></comp>
To cause this update to occur, the child must explicitly emit the event rather than directly mutating the property.
// wrong
this.foo = newValue;
// correct
this.$emit('update:foo', newValue);
Form Bindings
The v-model
directive can be used to create two-way data bindings on form <input>
and <textarea>
elements. These bindings automatically use the correct way to update the element based on its type.
The v-model
directive is essentially sugar for binding the variable to the input’s value with v-bind
and updating the variable whenever there is new input.
<input v-model="something">
<!-- Expands to -->
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
Therefore, for a component to work with v-model
, it should accept a value
prop and emit an input
event with the new value when appropriate.
It’s possible to specify something other than value
as the prop that is bound to and input
as the event that is emitted by using the model
option.
Vue.component('my-checkbox', {
model: {
// v-bind to prop `checked`
prop: 'checked',
// listen v-on `change`
event: 'change',
},
props: {
checked: Boolean,
// now `value` can be used for something else
value: String,
},
});
<my-checkbox v-model="foo" value="some value"></my-checkbox>
<!-- Expands to -->
<my-checkbox
v-bind:checked="foo"
v-on:change="(val) => { foo = val }"
value="some value">
</my-checkbox>
Note that v-model
ignores the initial value
, checked
, or selected
attributes. More simply, Vue treats the instance data as the single source of truth, so all initial values should be specified in the data
option.
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
If multiple checkboxes bind to the same array, the effect is that for each checkbox that is checked, its value
attribute is inserted into the array.
It’s possible to set an element’s value to something other than a string or boolean by using the v-bind
directive with a :value
, :true-value
, or :false-value
argument. This sets the v-model
to the specified value.
<input
type="checkbox"
v-model="toggle"
v-bind:true-value="a"
v-bind:false-value="b">
<!-- For a radio button -->
<input type="radio" v-model="pick" v-bind:value="a">
// when checked:
vm.toggle === vm.a
// when unchecked:
vm.toggle === vm.b
The .lazy
modifier makes the data synchronize on each change
event instead of on each input
event.
The .number
modifier specifies to automatically convert the input string to a number.
The .trim
modifier automatically trims the input string.
Components
A component can be registered globally with the Vue.component()
method which takes a tag name and options object. The W3C rules for custom tag names are that they are all lowercase and must contain a hyphen; while a good practice, Vue does not enforce these rules.
<div id="example">
<my-component></my-component>
</div>
Vue.component('my-component', {
template: '<div>A custom component!</div>',
});
// create a root instance
new Vue({
el: '#example',
});
A component can be registered locally for a particular Vue instance by specifying it in its components
property.
var Child = {
template: '<div>A custom component!</div>',
};
new Vue({
components: {
// only available in parent's template
'my-component': Child,
},
});
Since objects are passed by reference, the data
property of a component should be defined as a function that returns an object, rather than as an object itself, so as to prevent having multiple instances of the component mutating the same data object.
The preferred method of component communication is “props down, events up,” meaning that parent components should pass data down to child components via props, and child components may communicate with parents by emitting events that propagate up the component hierarchy.
Since each component has its own isolated scope, parents can’t directly access a child’s data. Instead, parents may pass data to a child component via props
. Each component explicitly declares which props
it accepts via the props
option property.
Vue.component('child', {
props: ['message'],
template: '<span>{{ message }}</span>',
});
<child message="hello!"></child>
Props can be dynamically bound to data on the parent with the v-bind
directive.
<div>
<input v-model="parentMsg">
<br>
<child v-bind:my-message="parentMsg"></child>
</div>
Prop values are interpreted as strings, unless they are applied to v-bind
in which case they are interpreted as a JavaScript expression:
<!-- this passes down a plain string "1" -->
<comp some-prop="1"></comp>
<!-- this passes down an actual number -->
<comp v-bind:some-prop="1"></comp>
Mutating the parent’s state is highly discouraged and may occur by inadvertently mutating an object or array passed via a prop, since both are passed by reference.
If the purpose of a prop is to serve as an initial value for an internal data property, it should be prefixed with the word initial
and used to set the initial value of that data property:
props: ['initialCounter'],
data() {
return { counter: this.initialCounter }
},
If the purpose of a prop is to read a raw value which the component uses to compute another, create a computed property for the target value:
props: ['size'],
computed: {
normalizedSize() {
return this.size.trim().toLowerCase();
}
},
If a parent passes an attribute that is not a declared prop to a child component, the attribute is applied to the child’s root element, replacing the root element’s corresponding attribute if any is found. However, if the attribute in question is a class
or style
attribute, the values are merged with the corresponding attributes on the root element, instead of replacing them.
Functional Components
A functional component is a stateless component marked with the option property functional
. It has no instance, it simply consists of a render
function that takes props
and children
as arguments and returns one or more VNodes (as an array), unlike stateful components that can only return a single root node. It can also specify an optional props
property, although all attributes found on the component node are implicitly extracted as props.
The context
object passed to the render function has many properties:
props
objectchildren
array of VNodesslots
function returning a slots objectdata
the data object passed to the componentparent
reference to parent componentlisteners
object of parent-registered event listeners (alias ofdata.on
)injections
containing the resolved injections
Note that functional components don’t show up in Vue devtools since they lack a persistent instance.
Vue.component('wrap-with-tag', {
functional: true,
props: ['tag'],
render (createElement, context) {
return createElement(context.props.tag, null, context.children);
},
});
<wrap-with-tag tag="div">hello</wrap-with-tag>
<!-- Produces -->
<div>hello</div>
Validation
It’s possible to define validation criteria for props by using an object instead of an array of prop names, where each key is the name of the prop and value is some validation criteria. That criteria can be a constructor function such as String
, Number
, or a custom one. An array of these can be used to specify multiple allowed types.
An object can be passed where the type
property specifies the allowed types, required
property declares that the prop must be specified, default
property specifies a value or function yielding a default value if none is given, and validator
property specifies a function that validates the actual value.
Vue.component('example', {
props: {
// basic type check (`null` means accept any type)
propA: Number,
// multiple possible types
propB: [String, Number],
// a required string
propC: {
type: String,
required: true,
},
// a number with default value
propD: {
type: Number,
default: 100,
},
// object/array defaults should be returned from a
// factory function
propE: {
type: Object,
default() {
return { message: 'hello' };
},
},
// custom validator function
propF: {
validator(value) {
return value > 10;
},
},
},
});
Content Distribution
It’s possible to parent content with a component’s own template via a process called content distribution. Content placed within a child component by the parent is discarded unless the child component template contains at least one <slot>
outlet.
If there is only one, without any attributes, then the entire content fragment is inserted at that position, replacing the slot itself. Anything inside of a <slot>
tag is considered fallback content which is rendered only if the hosting element is empty and has no content to be inserted.
<!-- Child -->
<div>
<h2>I'm the child title</h2>
<slot>
This will only be displayed if there is no content
to be distributed.
</slot>
</div>
<!-- Parent -->
<div>
<h1>I'm the parent title</h1>
<my-component>
<p>This is some original content</p>
<p>This is some more original content</p>
</my-component>
</div>
<!-- Rendered -->
<div>
<h1>I'm the parent title</h1>
<div>
<h2>I'm the child title</h2>
<p>This is some original content</p>
<p>This is some more original content</p>
</div>
</div>
It’s also possible to name slots via <slot>
’s name
attribute in order to specify how content should be distributed. The hosting element declares that a given element will be distributed to a <slot>
with a given name
attribute by specifying that name via a slot
attribute on an arbitrary element.
An unnamed, default slot may coexist as a catch-all for any unmatched content. If there is no default slot, unmatched content is discarded.
<!-- Child -->
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
<!-- Parent -->
<app-layout>
<h1 slot="header">Here might be a page title</h1>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<p slot="footer">Here's some contact info</p>
</app-layout>
<!-- Rendered -->
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
Scoped slots allow a child to expose data that the parent can use when filling that slot. To do this, the child simply passes the data via attributes on the <slot>
tag, binding if necessary. The parent must then fill that slot using a <template>
tag with a scope
attribute naming an object that will be created and populated with the exposed data, then the data becomes available within that <template>
as a property on that object.
<!-- Child -->
<div class="child">
<slot text="hello from child"></slot>
</div>
<!-- Parent -->
<div class="parent">
<child>
<template scope="props">
<span>hello from parent</span>
<span>{{ props.text }}</span>
</template>
</child>
</div>
<!-- Rendered -->
<div class="parent">
<div class="child">
<span>hello from parent</span>
<span>hello from child</span>
</div>
</div>
This can be used to allow a parent scope to customized the way that a child component renders list items.
<!-- Child -->
<ul>
<slot name="item"
v-for="item in items"
v-bind:text="item.text">
<!-- fallback content -->
<li>Default: {{ item.text }}</li>
</slot>
</ul>
<!-- Parent -->
<my-awesome-list v-bind:items="items">
<!-- scoped slot can be named too -->
<template slot="item" scope="props">
<li class="my-fancy-item">{{ props.text }}</li>
</template>
</my-awesome-list>
It’s possible to dynamically switch between multiple components mounted at the same point by using the reserved <component>
element and dynamically binding its is
attribute.
var vm = new Vue({
el: '#example',
data: { currentView: 'home' },
components: {
home: { … },
posts: { … },
archive: { … },
},
});
<component v-bind:is="currentView">
<!-- component changes when vm.currentView changes! -->
</component>
It’s also possible to bind to component objects directly:
var Home = { template: '<p>Welcome home!</p>' };
var vm = new Vue({
el: '#example',
data: { currentView: Home }
});
Components can be marked to be kept in memory in order to preserve their state and avoid re-rendering by wrapping it in a <keep-alive>
element.
<keep-alive>
<component :is="currentView">
<!-- inactive, switched-from components will be cached! -->
</component>
</keep-alive>
It’s possible to name a particular element and access it directly by using the ref
attribute on that element and accessing it via the $refs
instance property. If it’s used together with v-for
, the ref will be an array.
<div id="parent">
<user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' });
// access child component instance
var child = parent.$refs.profile;
It’s possible to define a component as a factory function that is resolved asynchronously via a callback or by returning a promise.
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);
});
Vue.component('async-promise-example', () => somePromise());
There is also a more advanced method of specifying asynchronous components by passing an options object that can specify the component
as a promise, a component to use while it is loading
, a delay
before showing that loading
component, a component to use in case of an error
, and a timeout
after which to show the error
component.
const AsyncComponent = () => ({
component: getSomeComponent(),
loading: LoadingComp,
error: ErrorComp,
delay: 200,
timeout: 3000,
});
Components can be recursive by specifying the name
option for them to refer to themselves recursively. The name
is automatically set when the component is registered globally.
Tools like Webpack may have difficulty importing cyclically-dependent components. For example, consider a Folder
component that has a child component FolderContents
, which itself may have a Folder
component within it. Webpack would see that Folder
imports FolderContents
, but FolderContents
needs to import Folder
—a cyclic dependency. This can be remedied by deferring the import, usually on the beforeCreate
lifecycle hook.
beforeCreate() {
this.$options.components.FolderContents = require('./folder-contents').default;
}
The inline-template
attribute can be applied to a child component so that its inner content is used as the child component’s template (in the child’s scope), rather than treating it as distributed content.
It’s possible to ensure that static HTML is only evaluated once by applying the v-once
attribute to the element.
Transitions
The <transition>
wrapper component can be used to add entering/leaving transitions to the contents for conditional rendering, conditional display, dynamic components, and component root nodes.
When a wrapped element is inserted or removed, Vue detects whether the element has CSS transitions or animations applied, and if so it adds or removes the CSS transition classes at the appropriate timings, and any JavaScript hooks are executed. Otherwise the DOM insertion/removal operations are executed on the next browser animation frame.
The possible transition classes include:
v-enter
: Starting to enter, before insertion until one frame after the element is inserted.v-enter-active
: Applied during the entire entering phase, before insertion until transition finishes.v-enter-to
: Ending enter, one frame after insertion (right afterv-enter
) until transition finishes.v-leave
: Starting to leave, as soon as transition is triggered until one frame.v-leave-active
: Applied during the entire leaving phase, as soon as transition is triggered until transition finishes.v-leave-to
: Ending leave, one frame after transition is triggered (right afterv-leave
is removed) until transition finishes.
The classes that are added or removed are prefixed by the <transition>
’s name
attribute and the v-
prefixed is removed, unless there is no name
attribute. A <transition>
with name="my-transition"
becomes my-transition-enter
.
The classes used can be overridden with attributes on the <transition>
tag which are named after the class in question with a -class
suffix, such as enter-active-class
.
Vue registers event listeners for the animation end on transitionend
or animatinoend
depending on the type used. If both are animation types are used, it is necessary to declare to Vue which one to care about by using the type
attribute on the <transition>
tag with a value of animation
or transition
.
Vue automatically detects when a transition has finished by using the transitionend
or animationend
events. However, an explicit duration can be specified with the duration
attribute, set to either an integer for milliseconds or an object with enter
and/or leave
properties specifying the enter or leave duration, respectively.
It’s possible to register JavaScript hooks to run at different stages of the transition by using v-on
with the name of the stage as an argument, e.g. v-on:after-enter
. These can be used to create JavaScript animations. Each hook is passed the root element within the <transition>
, while the enter
and leave
hooks are also passed a callback that should be invoked to signal the end of the phase.
If the appear
attribute is present in the <transition>
, the entering and leaving transitions are used when the element is rendered or is removed, or separate ones can be specified with an appear-
prefix, such as appear-active-class
. This can also be done for JavaScript hooks, such as v-on:after-appear
.
Here’s a complete example:
<div id="demo">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
new Vue({
el: '#demo',
data: { show: true },
});
.fade-enter-active, .fade-leave-active {
transition: opacity .5s
}
.fade-enter, .fade-leave-to {
opacity: 0
}
It’s possible to use <transition>
to transition between raw elements, such as between a v-if
and its v-else
, but care should be taken to add key
attributes to each in the event that they each use the same tag.
<transition>
<button v-if="isEditing" key="save">
Save
</button>
<button v-else key="edit">
Edit
</button>
</transition>
In fact, that can be condensed further by binding the key
attribute and using a ternary expression for the content:
<transition>
<button v-bind:key="isEditing">
{{ isEditing ? 'Save' : 'Edit' }}
</button>
</transition>
Transitioning between custom components simply entails the use of a dynamic <component>
.
By default, the entering and leaving transitions are simultaneous, meaning that if transitioning between two elements, one will begin entering at the same time that the other begins leaving. Other transition modes include in-out
where the current element transitions out until the new element has transitioned in, and out-in
where the current element transitions out and then the new element transitions in. These modes are specified via the mode
attribute on the <transition>
element.
The <transition-group>
element can be used to render multiple items. Unlike <transition>
, it renders a physical element which is <span>
by default (for example, for a list it can be <ul>
), and every element must have a unique key
attribute. The <transition-group>
element also adds a v-move
class to elements that are changing positions. This can be used in conjunction with a CSS transform
transition so that Vue automatically uses FLIP animations.
Mixins
Mixins are simply objects that contain component options which can be mixed into a component via its mixins
property:
const mixin = {
created() {
this.hello();
},
methods: {
hello() {
console.log('hello from mixin!');
},
},
}
const Component = Vue.extend({
mixins: [mixin],
});
new Component() // => "hello from mixin!"
Mixed-in hooks are called before the components hooks. Otherwise-conflicting options are sensibly mixed, except when merging properties that expect object values, such as methods
, in which case the component’s corresponding property overrides the mixin’s. When merging custom options, the existing value is overwritten unless a custom merge strategy is defined for that option.
Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) {
// return mergedVal
}
// Or just use `methods`'s strategy.
Vue.config.optionMergeStrategies.myOption = Vue.config.optionMergeStrategies.methods;
Although discouraged, it’s also possible inject mixins globally via Vue.mixin()
.
Custom Directives
Custom directives can be defined globally via the Vue.directive()
method.
// Define a `v-focus` directive.
Vue.directive('focus', {
// When the bound element is inserted into the DOM…
inserted(el) {
el.focus();
}
})
Custom directives may also be defined locally within a component via the directives
property.
directives: { focus: { … } }
<input v-focus>
A directive can hook into different points:
bind
: When the directive is first bound to the element.inserted
: When the element is inserted into its parent node (just parent node presence—not necessarily in-document).update
: When component’s VNode has updated, possibly before its children VNodes have. The binding’s old and new values can be checked to avoid unnecessary updates.componentUpdated
: When component’s VNode and the VNodes of its children have updated.unbind
: When the directive is unbound from the element.
Each hook is passed three arguments:
- The
element
the directive is bound to - The
binding
object. - The
vnode
(virtual node) of the element.
The update
and componentUpdated
hooks are also passed the oldVnode
, the previous virtual node.
The binding
object contains the following properties:
name
of the directive without thev-
prefixvalue
passed to the directive, specifically, the expression’s valueoldValue
whether or not it changed (forupdate
andcomponentUpdated
)expression
passed to the directive as a stringarg
passed to the directive, if anymodifiers
: an object mapping the provided modifiers totrue
There exists a shorthand for registering a custom directive that only executes on bind
and update
:
Vue.directive('color-swatch', (el, binding) => {
el.style.backgroundColor = binding.value;
});
Render Functions
It’s possible to write components that have explicit render functions instead of implicit ones via an associated templates.
Vue.component('anchored-heading', {
render: function (createElement) {
return createElement(
'h' + this.level, // tag name
this.$slots.default // array of children
);
},
props: {
level: {
type: Number,
required: true,
},
},
});
The createElement
function takes up to three arguments:
- The tag name, component options, or function returning either.
- An optional attributes object.
- The text string or array of children VNodes.
A virtual node is essentially a node description which. A virtual DOM is a tree of VNodes. Each VNode must be unique.
When defining event handlers directly within a component’s on
data object property, modifiers can be leveraged by using certain prefixes on the handler’s name:
Modifiers | Prefix |
---|---|
.passive |
& |
.capture |
! |
.once |
~ |
.capture.once |
~! |
on: {
'!click': this.captureMode,
'~keyup': this.doOnce,
`~!mouseover`: this.doOnceInCaptureMode,
},
Plugins
There are different types of plugins:
- Some add some global methods or properties
- Some add one or more global assets (directives, filters, transitions)
- Some add some component options via a global mixin
- Some add some Vue instance methods via
Vue
’s prototype - Some act as libraries with an API of their own
A plugin should expose an install
method which is called with the Vue
constructor as the first argument and possibly other options.
MyPlugin.install = function (Vue, options) {
// add global method or property (1)
Vue.myGlobalMethod = function () { … };
// or add a global asset (2)
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) { … },
});
// or inject some component options (3)
Vue.mixin({ created() { … } });
// or add an instance method (4)
Vue.prototype.$myMethod = function (methodOptions) { … },
};
A plugin is used via Vue.use()
with an optional options argument. This automatically guards against loading the same plugin more than once.
// calls `MyPlugin.install(Vue)`
Vue.use(MyPlugin)
// with options:
Vue.use(MyPlugin, { someOption: true })
Filters
Filters can be used in mustache interpolations and v-bind
expressions. Filters can be chained.
<!-- in mustaches -->
{{ message | capitalize }}
<!-- in v-bind -->
<div v-bind:id="rawId | formatId"></div>
Custom filters can be defined in the component’s filters
property, which is an object mapping filter names to functions that handle them. Each filter is passed the expression’s value.
filters: {
capitalize(value) {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
},
},
Since filters are just functions, they can be defined to take additional arguments, however, the filtered value is always the first argument.
Reactivity
Given a passed-in data
option, Vue automatically traverses the object and converts each property into getters and setters via Object.defineProperty
, allowing Vue to inject behavior such as threading dependency-tracking and change-notification when properties are accessed or modified. This is why vue-devtools may be more useful for inspecting properties, since browser log functions will output the getters and setters themselves.
Each component instance has a watcher instance that records as dependencies any properties that are touched during the component’s render. Then when a dependency’s setter is triggered it notifies the watcher which then causes a re-render.
Vue debounces multiple watcher triggers within the same event loop iteration, flushing the queue of changes on the next tick. It’s possible to do some work until after Vue has performed the DOM updates by registering a callback to run on the next tick via Vue.nextTick()
.
<div id="example">{{ message }}</div>
const vm = new Vue({
el: '#example',
data: { message: '123' },
});
vm.message = 'new message';
vm.$el.textContent === 'new message'; // => false
Vue.nextTick(function () {
vm.$el.textContent === 'new message'; // => true
});
// Or within an instance:
this.$nextTick(function () {
console.log(this.$el.textContent);
});
Error Handling
The Vue.config.errorHandler
property can be set to a function that will receive any emitted errors.
Vuex
Like Redux, Vue has a notion of a store. A store in Vue is reactive and is mutated by committing mutations.
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state) {
state.count++
},
},
});
// …
store.commit('increment');
store.state.count // => 1
Besides accessing a global Vuex store within a component, Vuex allows for the injection of the store into all child components of a given component by simply specifying the store to inject as the store
component option. This makes the store available via the this.$store
instance property.
const app = new Vue({
el: '#app',
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`,
});
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count() {
return this.$store.state.count;
},
},
};
Store state is usually wrapped in a component’s computed property, so that the property is recomputed automatically when the state changes. The mapState()
function helps with this repetitive task. If passed an array, it creates computed
properties for each state property.
computed: mapState({
count: (state) => state.count,
// String 'count' is the same as `(state) => state.count`
countAlias: 'count',
// Use a normal function in order to access local state with `this`.
countPlusLocalState(state) {
return state.count + this.localCount;
},
});
// Map this.count to store.state.count.
computed: mapState(['count']);
The object spread operator can be used to mix Vuex’ mapState()
with local computed properties.
computed: {
localComputed() { … },
...mapState({ … }),
},
Getters
The store can define getters which are essentially computed properties defined on the store itself. Like computed properties, these only re-evaluate when any dependencies have changed. Getters are passed the state as the first argument and all getters as the second argument.
const store = new Vuex.Store({
state: {
todos: [{ id: 1, text: '...', done: true }],
},
getters: {
doneTodos: (state, getters) => state.todos.filter((todo) => todo.done),
},
});
Getters can be used within components by accessing them directly.
computed: {
doneTodosCount() {
return this.$store.getters.doneTodosCount;
},
},
A getter can return a closure in order to take arbitrary arguments.
getters: {
getTodoById: (state, getters) => (id) => {
return state.todos.find((todo) => todo.id === id);
},
},
store.getters.getTodoById(1); // => { id: 1, text: '...', done: true }
Like the mapSetters()
helper function, the mapGetters()
function can be used to map local computed
properties to a store’s getters by passing an array with the name of the store’s getter, or an object mapping the desired local computed property name to the name of the store’s getter.
computed: {
...mapGetters(['doneTodosCount']),
// or
...mapGetters({ doneCount: 'doneTodosCount' }),
},
Mutations
State in a store can only be changed by committing a mutation. Each mutation has a type
named by a string and a handler
function which actually performs the state modifications. The handler function is passed the state object as the first argument and an optional additional payload
argument.
Mutations must be synchronous.
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment(state) {
state.count++;
},
},
});
// invoke as
store.commit('increment');
When a mutation needs additional arguments, it should be done by passing an object as the payload
.
mutations: {
increment(state, payload) {
state.count += payload.amount;
},
},
// invoke as
store.commit('increment', { amount: 10 });
It’s also possible to commit a mutation with a single object argument which specifies the mutation via a type
property. This is known as object-style commit.
store.commit({ type: 'increment', amount: 10 });
Since Vuex state is reactive it has the same reactivity caveats. All fields should be defined upfront, and any new properties on a nested object should either use Vue.set()
or should result in a new nested object that replaces the original via Object.assign()
or the spread operator.
Components can commit mutations by using this.$store.commit()
directly or by using the mapMutations()
helper function which maps component methods
to store mutations either by a simple array mapping names one-to-one or with an object which can be used to specify the local name. This works whether or not the mutation takes a payload.
methods: {
...mapMutations(['increment', 'incrementBy']),
// Map `this.add()` to `this.$store.commit('increment')`.
...mapMutations({ add: 'increment' }),
},
Actions
Actions are possibly-asynchronous functions that commit mutations. Actions are passed a context
object which exposes the commit()
function, the state
, and the getters
.
Whereas mutations are triggered via commit()
, actions are triggered via dispatch()
.
Like mutations, actions support payload format and object-style dispatch.
actions: {
increment(context) {
context.commit('increment')
},
},
Components can dispatch actions by directly accessing this.$store.dispatch()
or by using the mapActions()
helper function to map component methods
to dispatch calls, much like mapMutations()
.
methods: {
...mapActions(['increment', 'incrementBy']),
// Map `this.increment()` to `this.$store.commit('increment)`
...mapActions({ add: 'increment' })
}
An action can return a promise, which is also returned by dispatch()
.
actions: {
asyncAction({ commit }) {
commit('someMutation');
return Promise.resolve();
},
otherAsyncAction({ dispatch, commit }) {
return dispatch('asyncAction').then(() => commit('otherMutation'));
},
},
Modules
A store can be divided into separate modules
, each with its own state, mutations, actions, getters, and even other modules within it.
const moduleA = {
state: { … },
mutations: { … },
actions: { … },
getters: { … },
};
const moduleB = {
state: { … },
mutations: { … },
actions: { … },
};
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB,
},
});
store.state.a; // -> `moduleA`'s state
store.state.b; // -> `moduleB`'s state
A module’s mutations and getters are passed the module’s local state. Module actions get the module’s local state via context.state
, but the store’s root state is also accessible via context.rootState
. Module getters can access the root state via the third argument.
Although a module’s state is nested under a state property named after the module, actions, mutations, and getters are registered under the global namespace by default, allowing multiple modules to react to the same mutation or action type (when this happens, asynchronous actions yield a Promise that resolves when all triggered handlers have resolved).
This can be avoided by marking the module as namespaced
, then all getters, actions, and mutations are namespaced under the module name with a forward slash /
separating namespace components.
modules: {
account: {
namespaced: true,
// module state is already nested
state: { … },
// -> getters['account/isAdmin']
getters: { isAdmin () { … } },
// -> dispatch('account/login')
actions: { login () { … } },
// -> commit('account/login')
mutations: { login () { … } },
// nested modules
modules: {
// inherits the namespace from parent module
myPage: {
state: { … },
// -> getters['account/profile']
getters: { profile () { … } },
},
// further nest the namespace
posts: {
namespaced: true,
state: { … },
// -> getters['account/posts/popular']
getters: { popular () { … } },
},
},
},
},
Namespaced getters and actions receive localized getters
, dispatch
, and commit
, that is, ones that act upon the namespaced module itself. Global state and getters can be accessed via rootState
and rootGetters
which are passed as third and fourth arguments to getter functions and exposed via the context
object in action functions. Given a localized dispatch
or commit
function, global actions or commits can be invoked by passing { root: true }
as the third argument.
modules: {
foo: {
namespaced: true,
getters: {
someOtherGetter: (state) => { … },
someGetter(state, getters, rootState, rootGetters) {
// -> 'foo/someOtherGetter'
getters.someOtherGetter;
// -> 'someOtherGetter'
rootGetters.someOtherGetter;
},
},
actions: {
someAction({ dispatch, commit, getters, rootGetters }) {
// -> 'foo/someGetter'
getters.someGetter;
// -> 'someGetter'
rootGetters.someGetter;
// -> 'foo/someOtherAction'
dispatch('someOtherAction')
// -> 'someOtherAction'
dispatch('someOtherAction', null, { root: true })
// -> 'foo/someMutation'
commit('someMutation')
// -> 'someMutation'
commit('someMutation', null, { root: true })
},
someOtherAction(ctx, payload) { … }
},
},
},
The helper functions mapState()
, mapGetters()
, mapActions()
, and mapMutations()
each can take a namespace string as the first argument so that all bindings are done in that module’s context.
computed: {
...mapState({
a: (state) => state.some.nested.module.a,
b: (state) => state.some.nested.module.b,
}),
},
// or
computed: {
...mapState('some/nested/module', {
a: (state) => state.a,
b: (state) => state.b,
}),
},
methods: {
...mapActions([
'some/nested/module/foo',
'some/nested/module/bar',
]),
},
// or
methods: {
...mapActions('some/nested/module', [
'foo',
'bar',
]),
},
In fact, namespaced helpers can be created with createNamespacedHelpers()
, which returns an object with each helper method already bound to the given namespace.
import { createNamespacedHelpers } from 'vuex';
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module');
Modules can be dynamically registered via the store’s registerModule()
method, which takes the module name or an array that includes its namespace components, and the module itself. A dynamically registered module can be unregistered dynamically with the store’s unregisterModule()
method.
Modules can be made safely reusable by ensuring that the state
is a function that returns the state object, similar to a component’s data
property.
Plugins can be registered which can subscribe to mutations.
// Called on store initialization.
const myPlugin = (store) => {
// Called after every mutation.
store.subscribe((mutation, state) => {
// The mutation is object-style: `{ type, payload }`.
});
}
const store = new Vuex.Store({
plugins: [myPlugin],
…,
});
Plugins can commit mutations, allowing them to be used to sync a data source with the store.
Strict mode causes an error to be thrown when state mutation occurs outside of mutation handlers, and can be enabled on a store by setting its strict
property.
Using a Vuex store state value as a form input model with v-model
is incorrect since that would make Vue mutate the state directly instead of through mutations. Instead, a two-way computed property should be created to wrap around the state.
<input v-model="message">
computed: {
message: {
get() {
return this.$store.state.obj.message;
},
set(value) {
this.$store.commit('updateMessage', value);
},
},
},
The focus on unit testing a Vuex store should be on mutations and actions. Testing mutations simply entails importing them and passing them a mock state object. Testing actions can leverage inject-loader in order to mock API calls.
Router
The router is installed by instantiating a VueRouter
instance and giving it a list of route record, then passing that instance to Vue
’s router
property. A route’s match priority is determined by its order in the list of routes.
Vue router has a <router-link>
tag which can be used to link to a particular route and a <router-view>
tag which renders the component matched by the route. Active routes linked with <router-link>
automatically gain a .router-link-active
class.
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
],
});
const app = new Vue({ router }).$mount('#app');
Routes can be nested by leveraging a route record’s children
option, which is simply an array of route records. It’s possible to define an “index route” for a nested route by simply specifying an empty path
property.
A route record can be declared as a redirection to another by using the redirect
property which takes input similar to push()
: a string path or link descriptor object, or a function that is passed the target route and must return a redirect path or location.
const router = new VueRouter({
routes: [{ path: '/a', redirect: '/b' }],
});
It’s also possible to define a route as an alias to another by using the alias
property.
Dynamic route segments can be specified with a colon :
prefix. Vue router uses path-to-regexp under the hood.
The bound value for a dynamic segment is available within the matched component through the this.$route.params
object.
Routes can be named using the name
property and referred to by that name with the name
property on a link descriptor given to push()
.
Instead of tightly coupling a component to the router’s $route
instance property, necessary information should be passed through as props. This is accomplished by the route record’s props
property. The property can take on three types of values: true
which means to pass route.params
as the component’s entire props, an object specifying the props, or a function that is passed the route object and returns a props object.
const router = new VueRouter({
routes: [
{
path: '/search',
component: SearchUser,
props: (route) => ({ query: route.query.q })
},
],
});
It’s possible to have multiple named <router-view>
s via the name
attribute and each can receive different components when any one route matches, similar to template content distribution. the <router-view>
without any name
attribute is named default
.
In the example below, different components will be distributed to different <router-view>
s when the '/'
route matches.
<div class="view main">
<router-view></router-view>
</div>
<div class="view sidebar">
<router-view name="sidebar"></router-view>
</div>
<div class="view footer">
<router-view name="footer"></router-view>
</div>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: UserMain,
sidebar: UserOverview,
footer: UserStatistics,
},
},
],
});
When navigating between similar routes such as /user/:id
where only the id
differs, the same component will be re-used, meaning that lifecycle hooks will not be used. It is possible to react to navigation changes by adding a watch to $route
or by using the beforeRouteUpdate()
hook.
const User = {
template: '…',
beforeRouteUpdate(to, from, next) {
// React to route changes…
next();
}
}
Programmatic navigation is possible via methods on the router. The push()
method takes a string or location descriptor object, and is the function that is invoked with <router-link>
’s attribute to
. Note that the query
option is ignored when specifying a raw path
.
// path
router.push('home');
router.push({ path: 'home' });
// named route
router.push({ name: 'user', params: { userId: 123 }});
// with query, resulting in /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }});
The push()
method can also take optional onComplete
and onAbort
callbacks, in that order, which are called when navigation has successfully completed or has been aborted.`
The replace()
method works like push()
except that it doesn’t push a history entry.
The go(n)
method can go backwards or forwards in the history stack.
Vue router uses hash mode for its history simulation by default. This can be changed by setting VueRouter
’s mode
property to 'history'
for example.
Navigation guards are used to guard navigations either by redirecting or canceling it. Navigation can be hooked into globally, per-route, or in-component.
A global before-guard can be registered via router.beforeEach()
and is passed the route that is being navigated to
, the route navigating from
, and a callback used to resolve the hook, allowing for asynchronous guards. The way that the callback is called determines what happens next:
- no arguments: continue to next hook, or confirm the navigation if none are left
false
: abort the navigation/
or{ path: '/' }
: redirect elsewhereError
instance: abort the navigation and pass the error to error callbacks registered viarouter.onError()
A global guard can be registered with router.beforeResolve()
and is similar to a before-guard except that a regular guard is called right before the navigation is confirmed, that is, it can be assumed that the navigation has been confirmed, after all in-component guards and async route components are resolved.
A global after-guard can be registered with router.afterEach()
and it is not passed a resolution callback since the navigation has already completed by then.
Per-route before-enter guards can be specified with the route record’s beforeEnter
property.
In-component guards can be defined within a component:
beforeRouteEnter
: before navigation is confirmed; no access tothis
.beforeRouteUpdate
: when route changes but component is reused.beforeRouteLeave
: right before navigating away from this route.
Although beforeRouteEnter
doesn’t have access to this
because the instance hasn’t been created yet, it can pass a callback to the resolution callback.
beforeRouteEnter(to, from, next) {
next((vm) => {
// access to component instance via `vm`
});
},
Route record can define meta fields using the meta
property. Since route records can be nested, multiple routes can be matched for a given path. The list of matched records is accessible through the $route.matched
field. This field can be iterated on to check the meta fields.
For example, a meta field requiresAuth
can be processed such that if any of the matched routes contains that field and the user is not logged in, the navigation will be aborted and redirected to the login page.
router.beforeEach((to, from, next) => {
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath },
});
} else {
next();
}
} else {
next();
}
});
Since <router-view>
is essentially a dynamic component, <transition>
works as expected.
When using HTML5 history mode it’s possible to define a scroll behavior when navigating by defining a scrollBehavior
function which is passed the to
and from
route records as well as the savedPosition
and should return a scroll position object containing x
and y
properties or a selector
to scroll to and optional offset
coordinates object from it.
Resources
- vue-loader: Webpack loader for Vue