Vue提供了许多内置的指令 (例如 v-model
和 v-show
),同时也允许我们注册自定义的指令。
注意,在 Vue 中,代码复用和抽象的主要形式是组件,不是mixin,也不是插件和指令。
有的情况下,我们可能需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。比如聚焦输入框的例子。现在让我们用指令来实现这个功能:
const app = Vue.createApp({}) // 注册一个全局自定义指令 `v-focus` app.directive('focus', { // 当被绑定的元素挂载到 DOM 中时…… mounted(el) { // 聚焦元素 el.focus() } })
上面注册的是全局指令,如果想注册局部指令,在组件中添加一个 directives
的选项即可:
directives: { focus: { // 指令的定义 mounted(el) { el.focus() } } }
然后你可以在模板中任何元素上使用新的 v-focus
指令,如下所示:
<input v-focus />
v-是所有指令的前缀,用于表明它被Vue接管了。
要知道的是,自定义指令中,不是你想怎么做就能怎么做,它内部有一些固定套路,也就是Vue在这里设计了一些固定的用法。
在自定义指令中,我们可以做什么呢?最重要的就是往生命周期钩子函数中添加逻辑。
可以使用如下几个钩子函数 (均为可选):
created
:在绑定元素的属性或事件监听器被应用之前调用。beforeMount
:当指令第一次绑定到元素并且在挂载父组件之前调用。mounted
:在绑定元素的父组件被挂载后调用。beforeUpdate
:在更新包含组件的 VNode 之前调用。
updated
:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
beforeUnmount
:在卸载绑定元素的父组件之前调用unmounted
:当指令与元素解除绑定且父组件已卸载时,只调用一次。每个自定义指令钩子函数的都可以接收四个参数 (即 el
、binding
、vnode
和 prevVnode
)
指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value"
中,argument
参数可以根据组件实例数据进行更新!
看下面这个例子,通过创建一个自定义指令v-pin
,将元素固定在页面上,并以像素为单位更新被固定元素的垂直位置,如下所示:
<div id="dynamic-arguments-example" class="demo"> <p>Scroll down the page</p> <p v-pin="200">Stick me 200px from the top of the page</p> </div>
el
是要操作的DOM对象,binding
是绑定的参数:
const app = Vue.createApp({}) app.directive('pin', { mounted(el, binding) { el.style.position = 'fixed' // binding.value 是我们传递给指令的值——在这里是 200 el.style.top = binding.value + 'px' } }) app.mount('#dynamic-arguments-example')
这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。
<div id="dynamicexample"> <h3>Scroll down inside this section ↓</h3> <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p> </div>
const app = Vue.createApp({ data() { return { direction: 'right' } } }) app.directive('pin', { mounted(el, binding) { el.style.position = 'fixed' // binding.arg 是我们传递给指令的参数 const s = binding.arg || 'top' el.style[s] = binding.value + 'px' } }) app.mount('#dynamic-arguments-example')
为了使自定义指令更具动态性,我们还允许修改它的绑定值。让我们创建一个data属性 pinPadding
,并将其绑定到 <input type="range">
。
<div id="dynamicexample"> <h2>Scroll down the page</h2> <input type="range" min="0" max="500" v-model="pinPadding"> <p v-pin:[direction]="pinPadding">Stick me {{ pinPadding + 'px' }} from the {{ direction || 'top' }} of the page</p> </div>
const app = Vue.createApp({ data() { return { direction: 'right', pinPadding: 200 } } })
我们还可以通过添加updated钩子函数的方式,以在组件更新后重新计算固定的距离。
app.directive('pin', { mounted(el, binding) { el.style.position = 'fixed' const s = binding.arg || 'top' el.style[s] = binding.value + 'px' }, updated(el, binding) { const s = binding.arg || 'top' el.style[s] = binding.value + 'px' } })
如果指令需要多个参数值,可以传入一个 JavaScript 对象字面量。
记住,指令函数能够接受所有合法的 JavaScript 表达式。
<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!" })
和非 prop 的属性类似,当在组件中使用自定义指令时,它总是会被应用在组件的根节点上,而不是内部的所有叶节点。
<my-component v-demo="test"></my-component>
app.component('my-component', { template: ` <div> // v-demo 指令将会被应用在这里 <span>My component content</span> </div> ` })
当被应用在一个多根节点的组件上时,指令会被忽略,并且会抛出一个警告。