vue自定义指令 directive

652 阅读4分钟

前言

学习过程中的笔记,用于日后复习,如果有错误或者更好的方法,希望看到的大佬指出

vue中有很多常用的指令,比如v-on,v-if等等,也可以自定义一个指令来满足特殊的需求

此篇笔记将记录什么是自定义指令,什么情况下需要创建自定义指令以及如何在vue体系中创建自定义指令

什么情况下需要创建

为了保证Methods方法只有纯粹的数据逻辑(和DOM解耦,易于单元测试),不去处理DOM相关的操作,而将用于处理DOM的操作委托出来,约定成特定的修饰符,所以就是说,如果Methods中的方法如果需要操作DOM,就需要考虑自定义指令了

自定义指令

如何创建

Vue.directive("demo",{})

钩子函数

bind:function(el,binding,vnode){}
只会调用一次,指令第一次绑定到元素时调用,可以进行一次性的初始化设置,
此时 el.parentNodenull

inserted:function(el,binding,vnode){}
被绑定的元素插入父节点时调用(仅保证父节点存在,但是不一定插入到文档中)
el.parentNode 可以访问当前节点的父节点

update:function(el,binding,vnode,oldVnode){}
所在组件的vnode更新时调用,但是可能发生在其子vnode更新之前,
指令的值可能发生了变化,也可能没有,可以通过比较更新前后的值来忽略不必要的更新,从而一定程度的提高性能

componentUpdate:function(el,binding,vnode,oldVnode){}
指令所在组件的vnode以及其子vnode全部更新之后调用

unbind:function(el,binding,vnode){}
只调用一次,指令与元素解绑时调用

钩子函数参数

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

2-1 name :指令名,不好含v-前缀
2-2 value: 指令的绑定值,比如v-my-directive="1+1",绑定值为2
2-3 oldValue: 指令绑定的前一个值,仅在update和componentUpdate中可以使用
2-4 expression: 字符串形式的指令表达式 比如v-my-directive="1+1",表达式为"1+1"
2-5 arg:传给指令的参数 v-my-directive:foo 参数为foo
2-6 modifiers: 包含指令修饰符的对象 v-my-directive.stop.self ,modifires为{stop:true,self:true}
3 vnode vue编辑生成的虚拟节点
4 oldVnode 上一个虚拟节点,仅在update 和componentUpdate中可以使用

实际使用

下面有个需求,用来监听浏览器窗口宽度或者高度的变化,并且实时反应在页面上

基础代码

下面这段代码很简单,div上面绑定了自定义指令,触发时会调用onResize函数,获取数据

<template>
  <div v-getResize="onResize">浏览器{{ height }}</div>
</template>
export default {
  data() {
    return {
      direction: "1", //后续使用,1表示获取高度,2表示获取宽度
      height: 0,
    };
  },
  methods: {
    onResize(height) {
      this.height = height;
    },
  },
};

自定义指令

很简单,下面的代码就可以简单的实现浏览器改变高度的时候,获取浏览器的高度

//main.js
Vue.directive("getResize", {
  inserted(el, binding) {
    console.log(binding);
    let callback = binding.value;
    window.addEventListener("resize", () => {
      callback(window.innerHeight);
    });
  },
});

参数和修饰符

上面的代码并不能在页面刚刚打开的时候获取高度,只能在移动的时候获取,并且只能获取高度,而且也没有在unbind的时候,解除监听 所以我们需要传入一个参数来判断获取高度还是宽度,传入一个修饰符判断是否立即获取,并且在unbind的时候解除监听
需要注意的是,因为direction 传入的是变量,所以需要用[]包裹起来,还有removeEventListener里面的函数不能是匿名函数, 当然实际项目中,可能还需要加上防抖或者节流来优化代码,这里就不演示了

  <div v-getResize:[direction].quiet="onResize">浏览器{{ height }}</div>
  //main.js
  Vue.directive("getResize", {
  inserted(el, binding) {
    let callback = binding.value;
    let result = () => {
      return binding.arg == 1 ? window.innerHeight : window.innerWidth;
    };
    let onResize = () => {
      callback(result());
    };
    window.addEventListener("resize", onResize);
    if (binding.modifiers && binding.modifiers.quiet) onResize();
    el._onResize = onResize;
  },
  unbind(el) {
    if (!el._onResize) return;
    window.removeEventListener("resize", el._onResize);
    delete el._onResize;
  },
});

封装

由于上述指令是写在main.js中的,但是为了优雅一点点,还是不要直接写在里面的好

结构

resize.js

const resize = {
  inserted: function(el, binding) {
    //console.log(binding);
    let callback = binding.value;
    let arg = binding.arg;
    let modifiers = binding.modifiers;
    let result = () => {
      return arg === "1" ? window.innerHeight : window.innerWidth;
    };
    let onResize = () => {
      callback(result());
    };
    window.addEventListener("resize", () => {
      onResize();
    });
    if (modifiers && modifiers.quiet) {
      onResize();
    }
    el._onResize = onResize;
  },
  unbind: function(el) {
    if (!el._onResize) return;
    window.removeEventListener("resize", el._onResize);
    delete el._onResize;
  },
};
export default resize;

directive.js

import resize from "./resize";

export default {
  getResize: resize,
};

main.js

import directive from "./util/directive/directive";
for (const key in directive) {
  Vue.directive(key, directive[key]);
}

这就是全部代码,封装的方法纯粹是自己想出来写着玩的,如果有更好的方法,希望大佬指教