三个自定义指令demo,带你上手开发vue自定义指令

为了方便操作,我们都会封装一些全局指令为项目服务,这也会作为一个项目团队的基建。具体自定义指令请看这里

想要开发一个全局自定义指令,我们需要了解其生命周期钩子。

js 复制代码
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) {}
}
js 复制代码
<div v-example:foo.bar="baz">

binding 参数会是一个这样的对象:

js 复制代码
{
  arg: 'foo',
  modifiers: { bar: true },
  value: /* `baz` 的值 */,
  oldValue: /* 上一次更新时 `baz` 的值 */
}

注意,一般自定义指令不要使用在自定义组件上,如果该组件有多个根节点,那么vue将抛出一个警告。可能导致该指令不能达到预期效果。如果只有一个根节点那就无所谓了。

全局指令大体开发过程

我们一般在src目录下创建一个directives目录存放全局自定义指令。并提供一个index.js作为全部指令的出口文件,在main.js中统一注册。

js 复制代码
// 单独指令.js
function customDirective(app) {
    // TODO: 实现逻辑
}
export function setCustomDirective(app) {
  customDirective(app)
}
js 复制代码
// index.js
import { setCustomDirective } from './单独指令'

export default function setDirectives(app) {
  setCustomDirective(app)
}
js 复制代码
// main.js
import { createApp } from 'vue'
import App from './App.vue';
import setDirectives from "./directives";

const app = createApp(App);

(function(app){
  setDirectives(app)
  app.mount('#app')
})(app)

auth 按钮级权限校验指令

这个功能对于后台管理系统是十分常见的,我记得当时我在实习的时候还搞不懂为啥需要按钮权限,因为当时在学校做项目的时候根本没有那么多权限控制。

对于不同的角色,他会具有不同的操作权限。为了方便控制这些操作权限,我们就可以开发一个指令去控制按钮级别的元素的显隐。

大致的实现过程

  • 我们只需要使用mounted钩子即可。
  • 获取当前用户的操作权限列表,一般放在全局store中。
  • 支持指令的值是字符串或者数组,做适配处理。
  • 如果在当前用户的操作列表中未查到指定的权限,我们将直接移除当前元素。el.parentNode?.removeChild(el)
js 复制代码
function authDirective(app) {
  app.directive('auth', {
    mounted(el, bindings) {
      // TODO: 全局状态中获取当前用户的权限
      const permissions = ['a', 'b', 'c']
      const authArr = Array.isArray(bindings.value)
        ? bindings.value
        : [bindings.value]
      let isExist = false
      for (let item of authArr) {
        if (permissions.includes(item)) {
          isExist = true
          break
        } else {
          continue
        }
      }
      if (!isExist) el.parentNode?.removeChild(el)
    }
  })
}

export function setAuthDirective(app) {
  authDirective(app)
}

format-time 格式化时间

一般后端给我们返回的时间都是时间戳,或者返回的是完整时间,我们需要对齐格式化成我们想要的,就需要用到一些时间格式化库,例如dayjs。为了方便操作,不需要在setup中写太多逻辑,我们就可以直接封装成一个自定义指令。

大致步骤如下

  • created钩子中设置默认的格式。
  • mounted中处理逻辑,一般后台返回的都是字符串时间戳,我们需要转为数字才可以传入dayjs做格式化。
js 复制代码
import dayjs from 'dayjs'

function formatTimeDirective(app) {
  app.directive('format-time', {
    created(el, bindings) {
      bindings.formatString = 'YYYY-MM-DD'
      // 当用户传入格式化字符串,我们将使用用户传入的格式
      if (bindings.value) {
        bindings.formatString = bindings.value
      }
    },
    // 挂载时
    mounted(el, bindings) {
      let time = el.textContent
      // 如果是时间戳,转化为数字
      if (!Object.is(Number(time), NaN)) {
        time = Number(time)
      }
      el.textContent = dayjs(time).format(bindings.value)
    }
  })
}

export function setFormatTimeDirective(app) {
  formatTimeDirective(app)
}

lazy 图片懒加载

我们对于懒加载指令做了一下处理

  • 对图片做占位操作。
  • 对图片做未成功加载占位操作。
  • 只有当前元素可见时,才会去加载。

如果想了解更多图片懒加载请看这里

大致的实现过程

  • IntersectionObserverapi去判断当前元素是否可见。
  • new Image()的onload,onerror去判断指定的图片资源是否加载完毕或者成功。做统一的异步处理。
js 复制代码
// 先指定img元素占位图
el.setAttribute( 'src', 'https://lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/e08da34488b114bd4c665ba2fa520a31.svg' )
// 加载真实图片地址

// 图片加载完毕后,赋值真实地址
el.setAttribute('src', bindings.value)
// TODO: 错误图 图片加载失败时,赋值错误占位符
el.setAttribute(
  'src',
  'https://p26-passport.byteacctimg.com/img/user-avatar/cba9b94c077e932fb1ef7f9d5abd447b~50x50.awebp'
)
js 复制代码
function isExistContainer(el, bindings) {
  const observer = new IntersectionObserver(async (entires) => {
    // 当前元素可见
    if (entires[0].isIntersecting) {
      // 判断当前元素是否加载完毕
      const isLoaded = await isImgLoaded(el, bindings)
      if (isLoaded) {
        // 图片加载完毕后,赋值真实地址
        el.setAttribute('src', bindings.value)
      } else {
        // TODO: 错误图
        el.setAttribute(
          'src',
          'https://p26-passport.byteacctimg.com/img/user-avatar/cba9b94c077e932fb1ef7f9d5abd447b~50x50.awebp'
        )
      }
      observer.unobserve(el)
    }
  })
  // 监听
  observer.observe(el)
  return observer
}

async function isImgLoaded(el, bindings) {
  return new Promise((resolve) => {
    // TODO: 给当前元素设置占位图
    el.setAttribute(
      'src',
      'https://lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/e08da34488b114bd4c665ba2fa520a31.svg'
    )
    const img = new Image()
    // 赋值真实的路径
    img.src = bindings.value
    img.onload = () => {
      resolve(true)
    }
    img.onerror = () => {
      resolve(false)
    }
  })
}

function LazyDirective(app) {
  app.directive('lazy', {
    // 挂载时
    mounted(el, bindings) {
      // 判断是否在可视区域,并经行加载
      const observer = isExistContainer(el, bindings)
      bindings.observer = observer
    },
    // 取消监听
    unmounted(el, bindings) {
      bindings.observer.unobserve(el)
    }
  })
}

export function setLazyDirective(app) {
  LazyDirective(app)
}

通过这三个例子可以看出,我们开发简单的自定义指令来说都是使用mounted钩子即可。对于复杂一点的,我们还需要监听指令值的变化,需要用到updated钩子。

往期文章

专栏文章

相关推荐
天開神秀4 分钟前
解决 n8n 在 Windows 上安装社区节点时 `spawn npm ENOENT/EINVAL` 错误
前端·windows·npm
工业HMI实战笔记8 分钟前
图标标准化:一套可复用的工业图标库设计指南
前端·ui·性能优化·自动化·汽车·交互
2501_9269783315 分钟前
分形时空理论框架:从破缺悖论到意识宇宙的物理学新范式引言(理论概念版)--AGI理论系统基础1.1
java·服务器·前端·人工智能·经验分享·agi
We་ct17 分钟前
LeetCode 146. LRU缓存:题解+代码详解
前端·算法·leetcode·链表·缓存·typescript
SuperEugene1 小时前
数组查找与判断:find / some / every / includes 的正确用法
前端·javascript
孙笑川_1 小时前
Vue3 源码解析系列 1:从 Debugger 视角读 Vue
前端·vue.js·源码阅读
~央千澈~1 小时前
抖音弹幕游戏开发之第11集:礼物触发功能·优雅草云桧·卓伊凡
java·前端·python
top_designer1 小时前
Magnific:老旧 UI 糊成马?720p 截图重铸 4K 界面
前端·游戏·ui·prompt·aigc·设计师·游戏策划
Cache技术分享1 小时前
326. Java Stream API - 实现自定义的 toList() 与 toSet() 收集器
前端·后端
PythonFun1 小时前
WPS动态序号填充,告别手动调整烦恼
java·前端·python