彻底搞懂vue之自定义指令

10,863 阅读5分钟

一. 基本用法

1.钩子函数:

bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个绑定时执行一次的初始化动作。inserted:被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于document中)。update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。componentUpdated:被绑定元素所在模板完成一次更新周期时调用。unbind:只调用一次,指令与元素解绑时调用。

2.参数

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

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

  • binding

    :一个对象,包含以下属性:

    • name:指令名,不包括 v- 前缀。bingding.name

    • value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2

    • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。

    • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1"中,表达式为 "1 + 1"

    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"

    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }

  • vnode:Vue 编译生成的虚拟节点也就是虚拟dom。不了解虚拟dom的文档丢给你 vnode原理

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

ex1:

<div v-color="color"></div> 
//值可以是数字,数组,对象
//如果传字符串要加单引号,否则会认为是data中的变量
data(){
    return {
        color:"red"
    }  
},
directives:{
    color: {
        bind(el, binding) {
            el.style.color = bingding.value;//color为动态绑定的data中color的值
        }
    }
}

<div v-color:color="'red'"></div>
directives:{
     color: {
        bind(el, binding) {
            console.log(bing.arg);//绑定的参数名为color
            el.style.color = bingding.value;//value就是绑定的值,是字符串red
        }
    }
}

具体看官方文档

二. 两种使用方式

1.全局自定义组件

index.js

import Vue from "vue"
Vue.directive("color",{
    bind(el, binding) {
    	el.style.color=binding.value
    }
})

main.js中引入

import "@/directive"

2.组件中使用

import auth from '@/directive/auth'
export default {
  directives: { 
  	color:{
            bind(el, binding) {
    		el.style.color=binding.value
  	    }
        }
  }
}

三. 一些应用

1.auth

做权限验证时,我们需要根据用户身份来判定是否显示一些元素的显示,比如后台管理只有管理员才能修改文章,那么只有用户角色是管理员时,修改按钮才会显示,这时候就可以使用自定的指令(当然其他方法也可以)。

  • 自定义指令传入一个字符串比如<div v-auth="admin"></div>

  • 从cookie中取出用户的身份,并和传入的字符串做比较(当然也可将用户信息存入vuex从store中取)

  • 如果不相同,将元素的样式设置为display:none

    export default { directives: { auth: (el, binding) => { if (!getCookie("userName")) { el.style.display = "none";//先判断是否登陆,没登陆都不显示 } else { let roleName = getCookie("roleName");//用来区分用户角色 if (roleName === binding.value) { el.style.display = "block"; //判断当前用户角色和传入的角色是否相等,相等则显示 } else { el.style.display = "none"; } } } } }

注:上面这种写法是一种简写,因为bind和update的方法一致。

演示

2. Sticky吸顶

这个效果相信大家都不陌生,使用场景也不胜枚举,比如掘金文章详情页的目录就是一个经典的吸顶效果。废话不多说,先来分析下如何吸顶的逻辑

  • 在什么环境下触发——页面滚动

  • 在什么时候触发——滚动到元素距离顶部小于一段距离时

这样一分析就很简单了,页面滚动那无非就是监听下页面滚动事件,滚动获取元素距离顶部那就更简单了,一个API直接丢给你

mdn中getBoundingClientRect速查

export default {
  directives:{
    sticky:{
      bind(el,binding,vnode){
        function handler(e){
          let top=el.getBoundingClientRect().top
          if(top<=10){
            el.children[0].style.position="fixed"
            el.children[0].style.top="10px"
          }else{
            el.children[0].style.position="static"
            el.children[0].style.top="0"
          }
        }
        el._sticky={
          handler
        }
        document.addEventListener("scroll",handler,false)
      },
      unbind(el,binding){
        document.removeEventListener("scroll",el._sticky.handler,false)
        delete el._sticky.handler
      }
    }
}

演示

3. clickOutSide

这个效果其实是借鉴饿了么ui当中的实现,但是简化了其实现的思路,因为最近做到一个类似的使用场景所以就用自定义指令秀了一波操作。如果大家还想不到在哪会用到,去知乎点击top栏的通知(不是给知乎打广告)就一目了然了

不多bibi,实现原理也很简单,点击别处获得点击的dom元素,然后判断下拉框的元素是否包含该元素,包含该元素说明是在下拉框内点击的,所以不用隐藏下拉框;如果不包含该元素说明是在下拉框之外,故需要隐藏下拉框。

包含?这怎么判断,很简单一个API——contains搞定,不会的看这里已帮你服务到位

mdn中contains速查

当然做的时候也遇到几个小坑?

1.下拉框有个控制隐藏显示的按钮,但是这个按钮肯定是在下拉框外,所以如何避免点击按钮的时候强制隐藏下拉框(因为有时候点击按钮是为了显示下拉框)

很简单,在元素上加一个修饰符@click.stop就可以了

2.当下拉框隐藏后如何去掉这个点击事件?

我们都知道肯定使用removeEventListener这个API但是,如何确保和当初addEventListener时公用一个方法,不一致也没法移除啊!本来准备想在methods里定义一个方法,但是发现在自定义指令中访问不到this,头疼的丫匹,后来一想既然bind和unbind里都有el,那何不在el上新加个属性,然后在unbind中调用这个属性,调用完就立即将他删除,美滋滋迎刃而解!(后来才知道原来内部可以通过vnode.context访问vue实例,所以还是可以在methods里定义一个公用的方法的,原谅我才疏学浅)

export default {
  directives: {
    clickOutSide: {
      bind(el, binding, vnode) {
        function handler(e) {
          if (el.contains(e.target)) {
            return;
          } else {
            binding.value();
          }
        }
        el._clickOutSide = {
          handler
        };
        document.addEventListener("click", handler, false);
      },
      unbind(el, binding) {
        document.removeEventListener("click", el._clickOutSide.handler, false);
        delete el._clickOutSide;
      }
    }
  }
}

演示

最后

当然自定义指令还有很多应用场景,比如图片的懒加载,小伙伴们感兴趣可以自己做下噢,思路如下:

  • 先给图片一个默认的图片背景

  • 待加载完毕之后替换图片的src