Vue自定义指令(directive)及应用场景

270 阅读3分钟

directive指令是什么

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。 然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。

directive指令有什么用

实际应用-- 可以通过指令知道什么时候dom创建完成,从而进行依赖dom的库的初始化工作

directive指令怎么用

举个改变背景色的例子,如下:

<!--自定义指令 v-hello-->
<div v-hello="whichColor">123</div><br><br>

data() {
    return {
        whichColor: 'yellow',
    }
},
    
directives: {
    //指令的生命周期函数
    hello: {
        // 指令的定义
        inserted(el, binding, VNode) {
            el.style.background=binding.value
        },
    },
}

运行结果: 2022030801.png

  • el : 指令所绑定的元素,可以用来直接操作DOM
  • binding: 一个对象,包含指令的很多信息
  • vnode: 虚拟节点 image.png

详细介绍

自定义指令的生命周期(钩子函数)

钩子函数Vue 3说明
-created 新增!在元素的 attribute 或事件监听器被应用之前调用。
bindbeforeMount只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
insertedmounted被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
-beforeUpdate 新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似。
update移除!该钩子与 updated 有太多相似之处,因此它是多余的。请改用 updated所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。
componentUpdatedupdated指令所在组件的 VNode 及其子 VNode 全部更新后调用。
-beforeUnmount 新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
unbindunmounted只调用一次,指令与元素解绑时调用。

Vue 3 最终的API如下:

const MyDirective = {
  created(el, binding, vnode, prevVnode) {}, // 新增
  beforeMount() {},
  mounted() {},
  beforeUpdate() {}, // 新增
  updated() {},
  beforeUnmount() {}, // 新增
  unmounted() {}
}

函数的参数

指令钩子函数会被传入以下参数:

  • el:指令所绑定的元素,可以用来直接操作 DOM。

  • binding:一个对象,包含以下 property:

    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。。
    • arg:传给指令的参数,可选。
    • modifiers:一个包含修饰符的对象。
  • vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。

  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

为了说明以上参数实现的demo:

<h2>v-my-local</h2>
        <p v-my-local:argFoo.modifiersTest="{color:colorLocal,fontSize:fontSizeLocal}">我是v-my-local指令</p>
        
data() {
    return {
        colorLocal:'red',
        fontSizeLocal:'12',
    }
},

directives: {
    myLocal:{
        bind(el,binding,vnode) {
            console.log('myLocal bind')
            console.log(el,binding,vnode)
            el.style.color=binding.value.color
            el.style.fontSize=binding.value.fontSize+'px'
            console.log('------------------------------')
        },
        update(el, binding, vnode) {
            console.log('myLocal update')
            console.log(el,binding,vnode)
            el.style.color=binding.value.color
            el.style.fontSize=binding.value.fontSize+'px'
            console.log('------------------------------')
        }
    }
}

colorLocal 修改成 blue,运行结果如下:

image.png

image.png

使用场景

自动获取焦点

公司项目中,有自动获取焦点的需求。 虽然iview等组件库也提供了,自动获取焦点属性autofocus,但是使用是还是有问题。 如下:

<template>
  <div class="hello">
        <h2>autofocus</h2>
        autofocus: <Input v-model="value" autofocus placeholder="Enter something..." />
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
    props: {
        msg: String
    },
    data() {
        return {
            value: '',
        }
    },
    created() {
        window.d = this
    },
}
</script>

切换页面路由时,autofocus不起作用,刷新页面autofocus起作用。显然,这不是我们想要的效果。

使用directive指令实现:

<template>
  <div class="hello">

        <h2>v-focus</h2>
        v-focus  :  <Input v-model="value1" v-focus placeholder="Enter something..." />
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
    props: {
        msg: String
    },
    data() {
        return {
            value1: '',
        }
    },
    created() {
        window.d = this
    },
    directives: {
        focus: {
            inserted(el) {
                el.focus()
            },
        }
    }
}
</script>

直接操作底层dom获取焦点。无论是刷新页面还是切换路由都是自动获取焦点。

输入框输入韩文时,实时搜索

最后一个字还没输入完,也可以直接搜索 image.png

公司项目中,有这方面的改善需求。 使用directive指令实现如下:

<template>
    <h2>使用指令</h2>
    <Input v-model="value2" v-krSearch placeholder="Enter something..." />
    <ul>
        <li v-for="item in directiveData">{{item.text}}</li>
    </ul>
</template>

<script>
export default {
    computed: {
        directiveData () {
            let list = []
            list = _.filter(this.textData, (item, index) => {
               if(_.includes(item.text, this.tValue))    {
                   item.index = index
                   return true
               }
            })
            return list
        }
    },
    
    directives: {
        krSearch: {
            inserted(el,binding,vnode) {
                let _this = vnode.context
               
                const tInput = el.querySelector('input[type="text"]')
                tInput.addEventListener('input', (e) => {
                     _this.tValue = e.target.value
                })
            },
        }
    },
}
</script>

按钮级别权限控制

公司项目中,有封装过按钮组件

<template>
  <div>
      <h2>按钮级别权限控制</h2>
      <Button type="primary" v-has="'admin'">添加</Button> 
      <Button type="primary" v-has="'vip'">修改</Button> 
      <Button type="primary" v-has="'normal'">删除</Button> 
      <Button type="primary" v-has="'qqq'">取消</Button> 
      <Button type="primary" v-has>测试</Button> 
  </div>
</template>

<script>
  export default {
    name:'',
    components: {},
    data () {
      return {
          btnPermissions: ['admin','vip','normal']
      };
    },
    computed: {
      
    },
    watch: {
      
    },
    created() {
      
    },
    mounted() {
      
    },
    directives: {
        has: {
            inserted (el, binding, vnode) {
                let _this = vnode.context

                let btnPermissionsArr = _this.btnPermissions
                if (!_this.has(btnPermissionsArr, binding.value)) {
                    el.parentNode.removeChild(el)
                }
            },
        }
    },
    methods: {
        has(arr, btnPermissionsStr) {
            let isExist = false;

            if (btnPermissionsStr == undefined || btnPermissionsStr == null) {
                return false;
            }
            if (arr.indexOf(btnPermissionsStr) > -1) {
                isExist = true;
            }
            return isExist;
        }
    },
  }
</script>

image.png

自定义全局指令

以上例子全都是局部指令,当然也可以全局使用。

第一步在main.js中写入我们的全局指令

Vue.directive( 'focus', {
    inserted(el) {
        el.focus()
    },
 })

在子组件中,v-focus指令是可以正常使用。

<h2>v-focus</h2>
v-focus  :  <input v-model="value1" v-focus placeholder="Enter something..." />