10分钟学会vue的自定义指令

168 阅读5分钟

Vue初学者入门级,自定义指令 篇

除了日常我们用到v-for、v-if、v-html指令外,一旦我们需要作用于特定场景时,我们就可以使用自定义指令。
话不多说,直接上代码见识自定义指令的好用之处。
封装防抖按钮指令(v-throttle)

将写好的自定义指令通过全局或者局部注册之后,只需要在按钮上添加 v-throttle,就可以实现按钮防抖,说实话是不是很方便!!(指令名称可以随便设置,不一定是v-throttle)
如:<button @click="cli" v-throttle>防抖按钮</button>

功能展示

230150176232167134233162145.gif

下面是编写自定义指令的js文件,这里是单独拎出作为文件。

//  新建throttlew.js文件
const throttle={
    bind: (el, binding) => {
        let throttleTime = binding.value; // 防抖时间
        if (!throttleTime) { // 用户若不设置防抖时间,则默认2s
          throttleTime = 2000;
        }
        let cbFun;
        el.addEventListener('click', event => {
          if (!cbFun) { // 第一次执行
            cbFun = setTimeout(() => {
              cbFun = null;
            }, throttleTime);
          } else {
            event && event.stopImmediatePropagation();
          }
        }, true);
      },
}
export default throttle

所以自定义指令是如何创建并使用的呢

在vue中,提供了两种引入方式。分别是全局注册和局部注册。
全局注册 通过调用Vue.directive,第一个参数传入指令,无需携带前缀v-*,第二个参数传入对象,主要是一些钩子函数(后面会详细讲解)。

// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted: function (el) {
    // 聚焦元素
    el.focus()  // 页面加载完成之后自动让输入框获取到焦点的小功能
  }
})

局部注册 通过组件options选项中设置directive属性

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
    }
  }
}

钩子函数

自定义指令第二个参数传的是一个对象,提供了几个钩子函数(均可可选):

  • bind:只调用一次,在这里可以进行一次性的初始化设置。
  • inserted:被绑定元素插入父节点时调用。(仅保证父节点存在,但不一定已被插入文档中)
  • update:所在组件的VNode更新时调用,但是可能发生在其子 VNode 更新之前
  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  • unbind:只调用一次,指令与元素解绑时调用。

钩子函数参数

  • el :指令所绑定的元素,可以用来直接操作DOM。
  • binding:一个对象,包含以下property:
    • name:指令名,不包括v-前缀
    • 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 }

使用场景

  • 防抖
  • 水印
  • 一键Copy功能
  • 图片懒加载
水印

实现思路:传入需要想加入水印的dom元素,通过canvas绘制并装换为图片,通过背景图片插入。 功能展示: image.png 代码实现

const watermark = {
    bind:(el,binding)=>{
        const {value} = binding
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d');
        ctx.fillText(value,10, 50)
        const imgs = new Image()
        imgs.src = canvas.toDataURL("image/png");
        const parent = document.createElement('div')
        parent.style="width:100%;height:100%;position:absolute;top:0;left:0;background:url("+imgs.src+")"
        el.append(parent)
    }
}
export default watermark
图片懒加载

实现思路,指定install方法并传入loading图片进行挂载,通过判断是否兼容IntersectionObserver,否则监听滚动实现,在每个img标签加上自定义指令(v-lazy),并传入真实路径。 功能展示 230135146229138160232189189.gif 代码实现

const LazyLoad = {
    // install方法
    install(Vue,options){
    	  // 代替图片的loading图
        let defaultSrc = options.default;
        Vue.directive('lazy',{
            bind(el,binding){
                console.log(binding.value,defaultSrc)
                LazyLoad.init(el,binding.value,defaultSrc);
            },
            inserted(el){
                // 判断兼容处理
                if('IntersectionObserver' in window){
                    LazyLoad.observe(el);
                }else{
                    LazyLoad.listenerScroll(el);
                }
                
            },
        })
    },
    // 初始化
    init(el,val,def){
        // data-src 储存真实src
        el.setAttribute('data-src',val);
        // 设置src为loading图
        el.setAttribute('src',def);
    },
    // 利用IntersectionObserver监听el
    observe(el){
        let io = new IntersectionObserver(entries => {
            let realSrc = el.dataset.src;
            if(entries[0].isIntersecting){
                if(realSrc){
                    el.src = realSrc;
                    el.removeAttribute('data-src');
                }
            }
        });
        io.observe(el);
    },
    // 监听scroll事件
    listenerScroll(el){
        let handler = LazyLoad.throttle(LazyLoad.load,300);
        LazyLoad.load(el);
        window.addEventListener('scroll',() => {
            handler(el);
        });
    },
    // 加载真实图片
    load(el){
        let windowHeight = document.documentElement.clientHeight
        let elTop = el.getBoundingClientRect().top;
        let elBtm = el.getBoundingClientRect().bottom;
        let realSrc = el.dataset.src;
        if(elTop - windowHeight<0&&elBtm > 0){
            if(realSrc){
                el.src = realSrc;
                el.removeAttribute('data-src');
            }
        }
    },
    // 节流
    throttle(fn,delay){
        let timer; 
        let prevTime;
        return function(...args){
            let currTime = Date.now();
            let context = this;
            if(!prevTime) prevTime = currTime;
            clearTimeout(timer);
            
            if(currTime - prevTime > delay){
                prevTime = currTime;
                fn.apply(context,args);
                clearTimeout(timer);
                return;
            }
            timer = setTimeout(function(){
                prevTime = Date.now();
                timer = null;
                fn.apply(context,args);
            },delay);
        }
    }
}
export default LazyLoad;
一键copy功能

实现思路 通过自定义指令传入复制内容,并监听数据变化进行同步,调用document.execCommand()方法进行复制。

const Copy = { 
  bind(el, { value }) {
    el.$value = value; // 用一个全局属性来存传进来的值,因为这个值在别的钩子函数里还会用到
    el.handler = () => {
      if (!el.$value) {
      // 值为空的时候,给出提示,我这里的提示是用的 ant-design-vue 的提示,你们随意
        Message.warning('无复制内容');
        return;
      }
      // 动态创建 textarea 标签
      const textarea = document.createElement('textarea');
      // 将该 textarea 设为 readonly 防止 iOS 下自动唤起键盘,同时将 textarea 移出可视区域
      textarea.readOnly = 'readonly';
      textarea.style.position = 'absolute';
      textarea.style.left = '-9999px';
      // 将要 copy 的值赋给 textarea 标签的 value 属性
      textarea.value = el.$value;
      // 将 textarea 插入到 body 中
      document.body.appendChild(textarea);
      // 选中值并复制
      textarea.select();
      // textarea.setSelectionRange(0, textarea.value.length);
      const result = document.execCommand('Copy');
      if (result) {
        Message.success('复制成功');
      }
      document.body.removeChild(textarea);
    };
    // 绑定点击事件,就是所谓的一键 copy 啦
    el.addEventListener('click', el.handler);
  },
  // 当传进来的值更新的时候触发
  componentUpdated(el, { value }) {
    el.$value = value;
  },
  // 指令与元素解绑的时候,移除事件绑定
  unbind(el) {
    el.removeEventListener('click', el.handler);
  },
};
export default vCopy;

小小总结

初来乍到掘金,发布文章主要为了记录的自己学习过程,刚刚步入这个秃头、内卷行业,从校园到职场,希望勿忘初心,砥砺前行,顶峰相见。
最后,我是夏繁,大家端午节安康hhh
参考文献: vue3js.cn/interview/v…