Custom Directives
Introduction
Section titled “Introduction”In addition to the default set of directives shipped in core (like v-model
or v-show
), Vue also allows you to register your own custom directives.
Two forms of code reuse have been introduced in Vue: components and composables.
- Components are the main building blocks
- Composables are focused on reusing stateful logic.
Custom directives, on the other hand, are mainly intended for reusing logic that involves low-level DOM access on plain elements.
A custom directive is defined as an object containing lifecycle hooks similar to those of a component. The hooks receive the element the directive is bound to. Here is an example of a directive that adds a class to an element when it is inserted into the DOM by Vue:
const highlight = {mounted: (el) => el.classList.add('is-highlight')}
export default {directives: { // enables v-highlight in template highlight}}
<p v-highlight>This sentence is important!</p>
Similar to components, custom directives must be registered so that they can be used in templates. In the example above, we are using local registration via the directives
option.
It is also common to globally register custom directives at the app level:
app.directive('highlight', {mounted: (el) => el.classList.add('is-highlight')})
When to use custom directives
Section titled “When to use custom directives”Custom directives should only be used when the desired functionality can only be achieved via direct DOM manipulation.
A common example of this is a v-focus
custom directive that brings an element into focus.
const focus = {mounted: (el) => el.focus()}
export default {directives: { // enables v-focus in template focus}}
<input v-focus />
This directive is more useful than the autofocus
attribute because it works not just on page load - it also works when the element is dynamically inserted by Vue!
Declarative templating with built-in directives such as v-bind
is recommended when possible because they are more efficient and server-rendering friendly.
Directive Hooks
Section titled “Directive Hooks”A directive definition object can provide several hook functions (all optional):
const myDirective = {// called before bound element's attributes// or event listeners are appliedcreated(el, binding, vnode) { // see below for details on arguments},// called right before the element is inserted into the DOM.beforeMount(el, binding, vnode) {},// called when the bound element's parent component// and all its children are mounted.mounted(el, binding, vnode) {},// called before the parent component is updatedbeforeUpdate(el, binding, vnode, prevVnode) {},// called after the parent component and// all of its children have updatedupdated(el, binding, vnode, prevVnode) {},// called before the parent component is unmountedbeforeUnmount(el, binding, vnode) {},// called when the parent component is unmountedunmounted(el, binding, vnode) {}}
Hook Arguments
Section titled “Hook Arguments”- el: The element the directive is bound to. This can be used to directly manipulate the DOM.
- binding: An object containing the following properties:
- value: The value passed to the directive. For example in
v-my-directive="1 + 1"
, the value would be2
. - oldValue: The previous value, only available in
beforeUpdate
andupdated
. If the value is an object, it is the same object. - arg: The argument passed to the directive, if any. For example in
v-my-directive:foo
, the arg would be"foo"
. - modifiers: An object containing modifiers, if any. For example in
v-my-directive.foo.bar
, the modifiers object would be{ foo: true, bar: true }
. - instance: The instance of the component where the directive is used.
- dir: The directive definition object itself.
- value: The value passed to the directive. For example in
- vnode: The underlying VNode for the directive’s host element.
- prevVnode: The previous VNode, only available in
beforeUpdate
andupdated
.
As an example, consider the following directive usage:
<div v-example:foo.bar="baz"></div>
The binding argument would be an object in the shape of:
arg: 'foo', modifiers: { bar: true }, value: /* value of `baz` */, oldValue: /* value of `baz` on previous update */,
Similar to built-in directives, custom directive arguments can be dynamic. For example:
<div v-example:[arg]="value"></div>
Here the directive argument will be reactively updated based on arg
property in our component state.
Function Shorthand
Section titled “Function Shorthand”It’s common for a custom directive to have the same behavior for mounted
and updated
, with no need for the other hooks. In such cases we can define the directive as a function:
app.directive('color', (el, binding) => { // this will be called for both `mounted` and `updated` el.style.color = binding.value})
<div v-color="color"></div>
Object Literals
Section titled “Object Literals”If your directive needs multiple values, you can also pass in a JavaScript object literal. Remember, directives can take any valid JavaScript expression.
<div v-demo="{ color: 'white', text: 'hello!' }"></div>
app.directive('demo', (el, binding) => {console.log(binding.value.color) // => "white"console.log(binding.value.text) // => "hello!"})
Usage on Components
Section titled “Usage on Components”When used on components, custom directives will always apply to a component’s root node, similar to Fallthrough Attributes.
<MyComponent v-demo="test" />
<!-- template of MyComponent -->
<div> <!-- v-demo directive will be applied here --><span>My component content</span></div>
You will notice that components can potentially have more than one root node. When applied to a multi-root component, a directive will be ignored and a warning will be thrown. Unlike attributes, directives can’t be passed to a different element with v-bind="$attrs"
.