三个自定义指令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钩子。

往期文章

专栏文章

相关推荐
ComPDFKit5 分钟前
使用 React PDF 构建 React.js PDF 查看器的指南
前端·react.js·pdf
二川bro16 分钟前
深度解析Vue项目Webpack打包分包策略 从基础配置到高级优化,全面掌握性能优化核心技巧
vue.js·webpack·性能优化
Dragon Wu16 分钟前
taro 小程序 CoverImage Image src无法显示图片的问题
javascript·小程序·前端框架·taro
明长歌17 分钟前
HTML页面渲染过程
前端·html
搏博43 分钟前
Hbuilder X4.65新建vue3项目存在的问题以及解决办法
前端·javascript·vue.js·ecmascript
Maya动画技术1 小时前
ollama调用千问2.5-vl视频图片UI界面小程序分享
前端·python·计算机视觉·视觉检测
孩子 你要相信光1 小时前
vue3/vue2大屏适配
前端·javascript·vue.js
武昌库里写JAVA2 小时前
Iteration in Golang – How to Loop Through Data Structures in Go
java·vue.js·spring boot·学习·课程设计
xihaowen2 小时前
Android Edge-to-Edge
android·前端·edge
娃哈哈哈哈呀3 小时前
Vue 3 动态 ref 的使用方式(表格)
前端·javascript·vue.js