Vue: Custom Directives
2022-03-30
0. Intro
除了 default directive(比如 v-model
、v-show
)之外,我们也可以自己定义 directives。
Vue 有两种代码复用的方式:
- components: building blocks
- composables: reusing stateful logic
Custom directive
复用 logic that involves low-level DOM access on plain elements.
Custom directive 是一个包含类似于 component lifecycle hooks 的 object,hook 会接收到 directive 绑定的 element。
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.
Example - v-focus
A directive that focuses an input when the element is inserted into the DOM by Vue.
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.
<input v-focus />
类似 component,我们要先注册 custom directives 才能用于 templates,我们可以用 directives
option 来进行 local registration。
const focus = {
mounted: (el) => el.focus()
}
export default {
directives: {
// enables v-focus in template
focus
}
}
Example - Global Registration
我们也常常会在 app level global register custom directives.
const app = createApp({})
// make v-focus usable in all components
app.directive('focus', {
/* ... */
})
Note
只有当功能只能通过 direct DOM manipulation 实现时才使用 custom directives,一般来说都会优先使用 built-in directives whenever possible.
Custom directives should only be used when the desired functionality can only be achieved via direct DOM manipulation. Prefer declarative templating using built-in directives such as
v-bind
when possible because they are more efficient and server-rendering friendly.
1. Directive Hooks
一个 directive definition object 可以提供多个 hook function (all optional),跟 life cycle 相关。
const myDirective = {
// called before bound element's attributes
// or event listeners are applied
created(el, binding, vnode, prevVnode) {
// see below for details on arguments
},
// called right before the element is inserted into the DOM.
beforeMount() {},
// called when the bound element's parent component
// and all its children are mounted.
mounted() {},
// called before the parent component is updated
beforeUpdate() {},
// called after the parent component and
// all of its children have updated
updated() {},
// called before the parent component is unmounted
beforeUnmount() {},
// called when the parent component is unmounted
unmounted() {}
}
}
Hook Arguments
Directive hooks 可以传入这些 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 inv-my-directive="1 + 1"
, the value would be2
.oldValue
: The previous value, only available inbeforeUpdate
andupdated
. It is available whether or not the value has changed.arg
: The argument passed to the directive, if any. For example inv-my-directive:foo
, the arg would be"foo"
.modifiers
: An object containing modifiers, if any. For example inv-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.
vnode
: the underlying VNode representing the bound element.prevNode
: the VNode representing the bound element from the previous render. Only available in thebeforeUpdate
andupdated
hooks.
Example 1
template 长这样。
<div v-example:foo.bar="baz">
binding
argument 长这样。
{
arg: 'foo',
modifiers: { bar: true },
value: /* value of `baz` */,
oldValue: /* value of `baz` from previous update */
}
Example 2 - dynamic arguments
<div v-example:[arg]="value"></div>
Here the directive argument will be reactively updated based on arg
property in our component state.
Note
Apart from el
, you should treat these arguments as read-only and never modify them. If you need to share information across hooks, it is recommended to do so through element’s dataset
.
2. Function Shorthand
自定义 directive 经常和 mounted
以及 updated
有一样的 behavior,不需要其他的 hooks,在这种情况下我们可以 defin the directive as a function:
It’s common for a custom directive to have the same behavior for
mounted
andupdated
, with no need for the other hooks.
Example
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// this will be called for both `mounted` and `updated`
el.style.color = binding.value
})
3. Object Literals
如果你的 directive 需要多个 value,你需要用 JS object literal 来 pass value。
Directives 可以 take 任何有效的 JS expression。
Example
<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!"
})
4. Usage on Components
当用在 component 时,custom directives 会应用在 component 的 root node 上,类似 Fallthrough Attributes 。
Example
<!-- parent component -->
<MyComponent v-demo="test" />
<!-- template of MyComponent -->
<div> <!-- v-demo directive will be applied here -->
<span>My component content</span>
</div>
Note
因为 component 可能有多个 root,所以当 custom directive 用在这种 multi-root component 上时,directive 会被忽略,然后会显示一个 warning message.
与 attributes 不同的是,directives 不能通过 v-bind="$attrs"
被传入一个不同的 element。
通常来说,不推荐在 component 上使用 custom directives。
Note 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"
. In general, it is not recommended to use custom directives on components.