自定义指令

217 阅读3分钟

一、自定义指令基础

1、vue原生指令
  1. 样式示例:v-name(指令名称):arg(指令传递参数).stop(指令修饰符)=”value(指令值)“
  2. vue指令常见修饰符:
  • .stop - 调用 event.stopPropagation()。阻止事件冒泡
  • .prevent - 调用 event.preventDefault()。阻止事件默认行为
  • .native - 监听组件根元素的原生事件。
2、自定义指令包含多个钩子函数:
  1. bind:只调用一次,指令第一次绑定到元素时调用。进行一次性的初始化设置
  2. inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)
  3. update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
  4. componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
  5. unbind:只调用一次,指令与元素解绑时调用。 最经常用到的钩子函数:
vue.directive('自定义指令', {
  bind: function (el, binding, vnode) {
  }
  unbind: function (el, binding, vnode) {
  }
})
3、钩子函数参数
  1. el:代表着指令绑定的元素,直接操作DOM;
  2. binding:存在一堆的property
  • name:指令名称;
  • value:指令值,可以进行表达式计算;
  • expression:指令表达式。例如:v-my-directive="1 + 1",字符串形式;个人理解表达式也可以作为指令值
  • arg:指令参数;
  data () {
    return {
     arg: {
      name: 'love_myself',
      age: 22
     }
  }}
  • modifiers:指令修饰符。例如:v-my-directive.foo,修饰符对象为 { foo: true };
  • vnode:Vue编译生成的虚拟节点;
  • oldValue:指令绑定的前一个值:适用于update和componentUpdated钩子函数中;
  • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

二、封装常见的自定义指令

1、长按指令:[v-longtouch]

使用场景:长按图片、或长按二维码

import Vue from 'vue'
Vue.directive('longtouch', {
  bind: function (el, binding, vnode) {
    if (typeof binding.value !== function) {
     const componentName = vnode.context.name
     let warn = `[longtouch:] provided expression '${binding.expression}' is not a function, but has to be`
     if (componentName) { warn += `found in ${componentName}`}
     console.warn(warn)
    }
    let longtouchTimer = null
    const start = (e) => {
      // e.button === 0:左键
      if (e.type === 'click' && e.button !== 0) return
      longtouchTimer = setTimeout(() => {
        binding.value()
      }, 600)
    }
    const cancel = () => {
      if (longtouchTimer !== null) {
        clearTimeout(longtouchTimer)
        longtouchTimer = null
      }
    }
    el.addEventListener('touchstart', start)
    el.addEventListener('mousedown', start)

    el.addEventListener('click', cancel)
    el.addEventListener('touchend', cancel)
    el.addEventListener('touchcancel', cancel)
    el.addEventListener('mouseout', cancel)
  }
})
// 使用步骤与正常的v-指令用法是一样的

2、滑动指令:[v-scroll]

使用场景:下拉滑动页面的时候

import Vue from 'vue'
Vue.directive('scroll', {
  inserted: function (el, binding) {
    const f = function (evt) {
      if (binding.value(evt, el)) {
        window.removeEventListener('scroll', f)
      }
    }
    window.addEventListener('scroll', f)
  }
})
// 使用步骤与正常的v-指令用法是一样的

3、滑动固定指令[v-scrollFixed]

使用场景:滑动页面,使得某内容固定在合适位置

  1. 模式一:页面中自定义指令,直接使用
 // 自定义指令
 Vue.directive('scrollFixed', {
  bind: function (el, binding) {
    if (binding.modifiers.prevent) {
      el.addEventListener('touchmove', eventPreventDefault)
    }
    if (binding.modifiers.stop) {
      el.addEventListener('touchmove', eventStopPropagation)
    }
    fixedBody()
  },
  unbind: function (el, binding) {
    if (binding.modifiers.prevent) {
      el.removeEventListener('touchmove', eventPreventDefault)
    }
    if (binding.modifiers.stop) {
      el.removeEventListener('touchmove', eventStopPropagation)
    }
    looseBody()
  }
})
// 抽离方法
function eventPreventDefault (e) {
  e.preventDefault()
}

function eventStopPropagation (e) {
  e.stopPropagation()
}

function fixedBody () {
  var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
  document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;'
}

function looseBody () {
  var body = document.body
  var top = body.style.top
  body.style.position = ''
  body.style.width = ''
  // eslint-disable-next-line no-multi-assign
  document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top)
  body.style.top = ''
}
使用步骤与正常的v-指令用法是一样的
  1. 模式二:以插件形式封装OK,注入到vue实例上去,在自己组件中使用
// 1. 处理插件OK,导出去
    const scrollFixedPlugin = {
      install (Vue) {
        Vue.directive('scrollFixed', {
          bind: function (el, binding) {
            if (binding.modifiers.prevent) {
              el.addEventListener('touchmove', eventPreventDefault)
            }
            if (binding.modifiers.stop) {
              el.addEventListener('touchmove', eventStopPropagation)
            }
            fixedBody()
          },
          unbind: function (el, binding) {
            if (binding.modifiers.prevent) {
              el.removeEventListener('touchmove', eventPreventDefault)
            }
            if (binding.modifiers.stop) {
              el.removeEventListener('touchmove', eventStopPropagation)
            }
            looseBody()
          }
        })
      }
    }

    function eventPreventDefault (e) {
      e.preventDefault()
    }

    function eventStopPropagation (e) {
      e.stopPropagation()
    }

    function fixedBody () {
      var scrollTop = document.body.scrollTop || document.documentElement.scrollTop
      document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;'
    }

    function looseBody () {
      var body = document.body
      var top = body.style.top
      body.style.position = ''
      body.style.width = ''
      // eslint-disable-next-line no-multi-assign
      document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top)
      body.style.top = ''
    }
    export default scrollFixedPlugin
2. 注入vue实例,use实例
    import Vue from 'vue'
    import scrollFixedPlugin from '@/utils/scrollFixed.js'

    Vue.use(scrollFixedPlugin)
3. 使用方式
 .model(v-scrollFixed v-if="flag")