提示

本文主要讲解 Vue 中自定义指令。@ermo

# 自定义指令

# 简介

v-showv-bind 这些都是 Vue.js 的内置指令。如果需要对普通 DOM 元素进行底层操作就需要用到自定义指令。

下例子将自定义一个 v-focus 用于进行元素聚焦:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app">
        <input v-focus>
    </div>
    
    <script>
        Vue.directive('focus', {
            // 当被绑定元素被插入到 DOM 时
            inserted: function(el) {
                // 聚焦元素
                el.focus();
            }
        });
        new Vue({
            el: '#app'
        });
    </script>
</body>
</html>

也可以针对一个 Vue 实例自定义指令,这样的指令只能被当前实例使用:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app">
        <input type="text" v-focus>
    </div>
    <script>
        new Vue({
            el: '#app',
            directives: {
                focus: {
                    inserted: function(el) {
                        el.focus();
                    }
                }
            }
        });
    </script>
</body>
</html>

# 钩子函数

一个指令可以提供以下钩子函数:

  • bind:只调用一次,指令第一次绑定到元素时调用。这里可以进行元素初始化。
  • inserted:被绑定元素插入到父节点时使用。
  • update:所在组件的 VNode 更新时调用。可以通过比较更新前后的值来忽略不必要的模板更新。
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

钩子函数包含以下参数:

  • el:指令绑定的元素,可直接操作 DOM。
    • binding:指令名,不包括 v- 前缀。
    • value:指令绑定的值。
    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。
    • expression:字符串形式的指令表达式。
    • arg:传给指令的参数,可选。
    • modifiers:一个包含修饰符的对象。
  • vnode:Vue 编译生成的虚拟节点。
  • oldVnode:上一个虚拟节点。仅在 updatecomponentUpdated 钩子中可用。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app" v-ermo:hello.a.b="message">

    </div>
    <script>
        Vue.directive('ermo', {
            bind: function(el, binding, vnode) {
                var s = JSON.stringify;
                el.innerHTML = 
                    'name: ' + s(binding.name) + '<br>' +
                    'value: ' + s(binding.value) + '<br>' +
                    'expression: ' + s(binding.expression) + '<br>' +
                    'argument: ' + s(binding.arg) + '<br>' +
                    'modifiers: ' + s(binding.modifiers) + '<br>' +
                    'vnode keys: ' + Object.keys(vnode) + '<br>'
                    ;

            }
        });
        new Vue({
            el: '#app',
            data: {
                message: 'hello'
            }
        });
    </script>
</body>
</html>

上例在页面的返回:

name: "ermo"
value: "hello"
expression: "message"
argument: "hello"
modifiers: {"a":true,"b":true}
vnode keys: tag,data,children,text,elm,ns,context,fnContext,fnOptions,fnScopeId,key,componentOptions,componentInstance,parent,raw,isStatic,isRootInsert,isComment,isCloned,isOnce,asyncFactory,asyncMeta,isAsyncPlaceholder

# 动态参数

指令的参数可以是动态的,语法为 v-mydirective:[arg]="value"arg 参数可以根据组件实例数据进行更新。

下例中,自定义一个指令 v-pin,使用的元素可以固定到距顶部 200px 位置:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app">
        <p>向下滑动网页</p>
        <p v-pin="200">当前内容相对顶部固定在200px位置</p>
    </div>
    <script>
        Vue.directive('pin', {
            bind: function(el, binding, vnode) {
                el.style.position = 'fixed'
                el.style.top = binding.value + 'px'
            }
        })
        new Vue({
            el: '#app'
        })
    </script>
</body>
</html>

如果希望固定的位置是从左侧计算而不是顶部,就需要用到动态参数:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app">
        <p>向下滚动网页</p>
        <p v-pin:[direction]="200">固定内容</p>
    </div>
    <script>
        Vue.directive('pin', {
            bind: function(el, binding, vnode) {
                el.style.position = 'fixed'
                
                if (binding.arg === 'left') {
                    el.style.left = binding.value + 'px'
                } else {
                    el.style.top = binding.value + 'px'
                }
                
            }
        })
        new Vue({
            el: '#app',
            data: function() {
                return {
                    direction: 'left'
                }
            }
        })
    </script>
</body>
</html>

# 函数简写

有时间,想在 bindupdate 时触发相同行为,而不关心其他钩子函数。可以使用简写方式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app">
        <p v-color="bgColor">一段文字</p>
    </div>
    <script>
        Vue.directive('color', function(el, binding) {
            el.style.backgroundColor = binding.value
        })
        new Vue({
            el: '#app',
            data: {
                bgColor: 'skyblue'    
            }
            
        })
    </script>
</body>
</html>

# 对象字面量

如果自定义指令需要传入多个值,可以使用对象字面量,对象字面量必须是一个标准的 JavaScript 对象:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script>
</head>
<body>
    <div id="app">
        <p v-color="{bgColor: 'skyblue', fontColor: 'pink'}">一段文字</p>
    </div>
    <script>
        Vue.directive('color', function(el, binding) {
            el.style.color = binding.value.fontColor
            el.style.backgroundColor = binding.value.bgColor
        })
        new Vue({
            el: '#app'
        })
    </script>
</body>
</html>
上次更新: 2/24/2023, 8:18:12 PM