Vue3实现节流自定义指令

248 阅读3分钟

1.介绍

Vue的自定义指令是一种扩展Vue模板中的HTML元素及其属性的方法。通过自定义指令,我们可以在元素上绑定自定义JavaScript行为和样式。 自定义指令在Vue应用程序中起着重要作用。 Vue内置了一些常见的指令(例如v-ifv-for等),但是出于某些业务需求可能需要编写自己的指令,以便在整个应用程序中重复使用它们。自定义指令可以让模板保持干净和简单,并提高代码的可重用性和清晰度。

2.如何实现

一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。如下vFocus

2.1注入当前组件

<script setup>
// 在模板中启用 v-focus
const vFocus = {
  mounted: (el) => el.focus()
}
</script>

<template>
  <input v-focus />
</template>

2.2全局注入

在实际开发中,将一个自定义指令全局注册到应用层级更常见(例如v-ifv-for等)

const app = createApp(App)
app.directive("throttle",throttleDirective)

使用app.directive来注册全局的自定义指令。throttleDirective是一个包含类似组件生命周期钩子的对象。

2.3指令的声明周期

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

2.4钩子参数

指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 **DOM,**可以用来获取当前绑定的DOM。

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

    • value:传递给指令的值。例如在 v-throttle:mouseover = "mouseover" 中,值是 mouseover。(该mouseover是在vue文件中定义的函数)
    • oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-throttle:mouseover 中,参数是 "mouseover"。
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。

  • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

3.实现自定义节流指令

在项目开发中,遇到些频繁触发的场景,常常需要使用节流函数来约束该行为每个某ms触发一次,防止频繁的函数调用,影响性能。自定义指令的简单实现如下:

export default {
  mounted: (el: any, binding: any, vnode: any) => {
    let cbFn = rafThrottle(() => {
        binding.value(el);
      })
    el.addEventListener(
      binding.arg,
      cbFn
    );
  },
  
};
function rafThrottle(func: Function) {
  let lock = false;
  return function (...args: any[]) {
    if (lock) return;
    lock = true;
    window.requestAnimationFrame(() => {
      //@ts-ignore
      func.apply(this, args);
      lock = false;
    });
  };
}
<div v-throttle:mouseover = "mouseoverFn"></div>

其中,binding.arg获取到该指令v-throttle:mouseover中mouseover,此处的mouseover仅仅只是一个用于addEventListener的事件名称。binding.value可以获取传递给指令的参数,此处传入的是vue文件中定义好的mouseoverFn函数,制定了该节流指令需要对mouseover触发的mouseoverFn函数做约束。