自定义指令

    当页面加载时,该元素将获得焦点 (注意:autofocus 在移动版 Safari 上不工作)。事实上,只要你在打开这个页面后还没点击过任何内容,这个输入框就应当还是处于聚焦状态。此外,你可以单击 Rerun 按钮,输入将被聚焦。

    现在让我们用指令来实现这个功能:

    如果想注册局部指令,组件中也接受一个 directives 的选项:

    1. directives: {
    2. focus: {
    3. // 指令的定义
    4. mounted(el) {
    5. el.focus()
    6. }
    7. }
    8. }

    然后你可以在模板中任何元素上使用新的 v-focus property,如下:

    1. <input v-focus />

    一个指令定义对象可以提供如下几个钩子函数 (均为可选):

    • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。在这里你可以做一次性的初始化设置。

    • mounted:在挂载绑定元素的父组件时调用。

    • beforeUpdate:在更新包含组件的 VNode 之前调用。

    提示

    • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。

    • beforeUnmount:在卸载绑定元素的父组件之前调用

    • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。

    接下来我们来看一下在 钩子函数的参数 (即 elbindingvnodeprevNnode)

    指令的参数可以是动态的。例如,在 v-mydirective:[argument]="value" 中,argument 参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

    例如你想要创建一个自定义指令,用来通过固定布局将元素固定在页面上。我们可以像这样创建一个通过指令值来更新竖直位置像素值的自定义指令:

    1. <div id="dynamic-arguments-example" class="demo">
    2. <p>Scroll down the page</p>
    3. <p v-pin="200">Stick me 200px from the top of the page</p>
    4. </div>
    1. const app = Vue.createApp({})
    2. app.directive('pin', {
    3. // binding.value is the value we pass to directive - in this case, it's 200
    4. el.style.top = binding.value + 'px'
    5. }
    6. })
    7. app.mount('#dynamic-arguments-example')

    这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。

    1. const app = Vue.createApp({
    2. data() {
    3. return {
    4. direction: 'right'
    5. }
    6. }
    7. })
    8. app.directive('pin', {
    9. mounted(el, binding) {
    10. el.style.position = 'fixed'
    11. // binding.arg is an argument we pass to directive
    12. const s = binding.arg || 'top'
    13. el.style[s] = binding.value + 'px'
    14. }
    15. })
    16. app.mount('#dynamic-arguments-example')

    结果:

    我们的定制指令现在已经足够灵活,可以支持一些不同的用例。为了使其更具动态性,我们还可以允许修改绑定值。让我们创建一个附加属性 pinPadding,并将其绑定到 <input type="range">

    1. <div id="dynamicexample">
    2. <h2>Scroll down the page</h2>
    3. <input type="range" min="0" max="500" v-model="pinPadding">
    4. </div>
    1. const app = Vue.createApp({
    2. data() {
    3. return {
    4. pinPadding: 200
    5. }
    6. }
    7. })
    1. app.directive('pin', {
    2. mounted(el, binding) {
    3. el.style.position = 'fixed'
    4. const s = binding.arg || 'top'
    5. el.style[s] = binding.value + 'px'
    6. },
    7. updated(el, binding) {
    8. const s = binding.arg || 'top'
    9. el.style[s] = binding.value + 'px'
    10. }
    11. })

    结果:

    在很多时候,你可能想在 mountedupdated 时触发相同行为,而不关心其它的钩子。比如这样写:

    如果指令需要多个值,可以传入一个 JavaScript 对象字面量。记住,指令函数能够接受所有合法的 JavaScript 表达式。

    1. <div v-demo="{ color: 'white', text: 'hello!' }"></div>
    1. app.directive('demo', (el, binding) => {
    2. console.log(binding.value.color) // => "white"
    3. console.log(binding.value.text) // => "hello!"
    4. })

    在 3.0 中,有了片段支持,组件可能有多个根节点。如果在具有多个根节点的组件上使用自定义指令,则会产生问题。

    要解释自定义指令如何在 3.0 中的组件上工作的详细信息,我们首先需要了解自定义指令在 3.0 中是如何编译的。对于这样的指令:

    1. <div v-demo="test"></div>

    将大概编译成:

    1. const vDemo = resolveDirective('demo')

    其中 vDemo 是用户编写的指令对象,其中包含 mountedupdated 等钩子。

    withDirectives 返回一个克隆的 VNode,其中用户钩子被包装并作为 VNode 生命周期钩子注入 (请参见渲染函数更多详情):

    因此,自定义指令作为 VNode 数据的一部分完全包含在内。当在组件上使用自定义指令时,这些 onVnodeXXX 钩子作为无关的 prop 传递给组件,并以 this.$attrs 结束

    1. <div @vnodeMounted="myHook" />

    这与 。因此,组件上自定义指令的规则将与其他无关 attribute 相同:由子组件决定在哪里以及是否应用它。当子组件在内部元素上使用 v-bind="$attrs" 时,它也将应用对其使用的任何自定义指令。