Vue进阶(四)自定义指令,并使用自定义指令实现防抖节流

除了核心功能默认内置的指令 (v-model 和 v-show),Vue 也允许注册自定义指令。注意,在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。
注意:这里官方对自定义指令有明确的说明,所以我们应该尽量去遵守,而不应该盲目的滥用自定义指令去完成一些任务

1. 指令

Vue为我们添加了很多内置指令,我们可以直接使用,例如v-on,v-bind,v-model,形如v-xx

2. 自定义指令

假设我们现在需要在进入一个页面后,使页面的一个输入框自动获得焦点:

<template>
  <div>
    <input type="text" placeholder="这里没有使用自定义指令"></input>
    <input v-focus type="text" placeholder="这里使用了自定义指令"></input>
  </div>
</template>

我们可以在全局或局部定义这个自定义指令:

2.1 全局自定义指令

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

2.2 局部自定义指令

组将可以接受一个directives选项:

directives: {
  focus: {
    // 指令的定义
    inserted: function (el) {
      el.focus()
    }
  }
}

3. 自定义指令的钩子

自定义指令的钩子也就是一个自定义指令对象的生命周期函数。

官方文档中说明:
一个指令定义对象可以提供如下几个钩子函数 (均为可选):

bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。

componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

unbind:只调用一次,指令与元素解绑时调用。

为了加深对这几个钩子函数的理解,我们测试一个小demo:

<template>
  <div>
    <input v-check type="text" placeholder="这里使用了自定义指令" v-model="value" v-if="v_if" v-show="v_show"></input>
    <button @click="changeValue">changeValue</button>
    <button @click="changeShow">changeShow</button>
    <button @click="changeIf">changeIf</button>
  </div>
</template>
<script>
export default {
  name: 'app',
  directives:{
    check: {
      bind: function () {
        console.log('bind')
      },
      inserted: function () {
        console.log('inserted')
      },
      update: function () {
        console.log('update')
      },
      componentUpdated: function () {
        console.log('componentUpdated')
      },
      unbind: function () {
        console.log('unbind')
      }
    }
  },
  methods:{
    changeValue () {
      this.value = 'changed'
    },
    changeShow () {
      this.v_show = !this.v_show
    },
    changeIf () {
      this.v_if = !this.v_if
    }
  },
  data () {
    return {
      value: '',
      v_if: true,
      v_show: true
    }
  },

当页面被加载出来时,控制台输出了:
在这里插入图片描述
当我们点击changeValue按钮之后,控制台输出了:
在这里插入图片描述
当我们点击changeShow按钮之后,控制台输出了:
在这里插入图片描述
之所以changeValuechangeShow会在控制台显示相同的内容,是因为这两个触发事件都改变了组件的视图,视图更新之前触发了update钩子,更新完成之后触发了componentUpdated钩子。
当我们点击changeIf按钮之后,控制台输出了:
在这里插入图片描述
指令钩子函数会被传入以下参数:
el:指令所绑定的元素,可以用来直接操作 DOM。
binding:一个对象,包含以下 property:

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

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

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

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 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。

假设我们现在存在一个这样的自定义指令:

Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '
'
+ 'value: ' + s(binding.value) + '
'
+ 'expression: ' + s(binding.expression) + '
'
+ 'argument: ' + s(binding.arg) + '
'
+ 'modifiers: ' + s(binding.modifiers) + '
'
+ 'vnode keys: ' + Object.keys(vnode).join(', ') } }) new Vue({ el: '#hook-arguments-example', data: { message: 'hello!' } })

模板:

<div id="hook-arguments-example" v-demo:foo.a.b="message"></div>

显示结果:
Vue进阶(四)自定义指令,并使用自定义指令实现防抖节流_第1张图片

注意:

  1. 除 update 与 componentUpdated 钩子函数之外,每个钩子函数都只含有 el、binding、vnode 这三个参数

  2. 在每个函数中,第一个参数永远是 el, 表示被绑定了指令的那个 dom 元素,这个el 参数,是一个原生的 JS 对象,所以 Vue 自定义指令可以用来直接和 DOM 打交道

  3. binding 是一个对象,它包含以下属性:name、value、oldValue、expression、arg、modifiers

  4. oldVnode 只有在 update 与 componentUpdated 钩子中生效

  5. 除了 el 之外,binding、vnode 属性都是只读的

4. 使用自定义指令实现防抖节流

这里我们使用的防抖节流函数来自笔者的另一篇博客:JavaScript 防抖(debounce) 和 节流(throttling),如果有疑问,可以查看该博客加深理解。
util/util.js:

function debounce(fn,delay,immediate=false){
  let timer = null
  let triggerTime // 触发fn的时间

  let run = (wait) => {
    console.log('create timer')
    timer = setTimeout(()=>{
      let executeTime = (new Date()).getTime() // 执行fn的时间
      let alreadyWait = executeTime - triggerTime
      if (alreadyWait < wait){
        run(delay - alreadyWait)
      } else {
        if (!immediate){
          fn()
        }
        timer = null
      }
    }, wait)
  }

  return () => {
    triggerTime = (new Date()).getTime()
    if (!timer) {
      if (immediate){
        fn()
      }
      run(delay)
    }
  }
}

function throttling(fn,delay,immediate=false){
  let timer = null

  return () => {
    if (!timer){
      if (immediate){
        fn()
      }
      timer = setTimeout(()=>{
        if (!immediate) fn()
        timer = null
      },delay)
    }
  }
}

export { debounce,throttling }

app.vue:

<template>
  <div>
    <span>{{value}}</span>
    <button v-optimize:debounce="{emit:'click',fn:addValue,delay:3000}">add</button>
  </div>
</template>
<script>
import { debounce, throttling } from './util/util'
export default {
  name: 'app',
  directives:{
    optimize: {
      bind: function (el, binding) {
      	// 根据binding.value获取参数
        const { emit,fn,delay=1000,immediate=false} = binding.value
        if (binding.arg==='debounce'){
          el.addEventListener(emit, debounce(fn,delay,immediate))
        } else {
          el.addEventListener(emit,throttling(fn,delay,immediate))
        }
      }
    }
  },
  methods:{
    addValue(){
      this.value ++
    }
  },
  data () {
    return {
      value: 0,
    }
  }
}
</script>

我们是如何向自定义指令传入参数的?v-optimize:debounce="{emit:'click',fn:addValue,delay:3000}",在自定义指令的钩子函数中,binding.arg就是debouncebinding.value就是 {emit:‘click’,fn:addValue,delay:3000} 。然后,根据自定义指令的参数,来选择防抖还是节流。

你可能感兴趣的:(vue,vue.js,javascript,前端)