【Vue】自定义指令directives && 指令钩子 && IntersectionObserver

文章目录


自定义指令

一、基本使用:directives

除了 Vue 内置的一系列指令 (比如 v-modelv-show) 之外,Vue 还允许你注册自定义的指令 (Custom Directives)。

在不使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册:

  1. 注册:

    javascript 复制代码
       // main.js文件
       
       app.directive('指令名', {
         mounted(el) {
           // el: 指令所在的DOM元素
         }
       })
  2. 使用:

    javascript 复制代码
       <p v-指令名></p>

💥注意事项: 元素挂载后 (成为DOM树的一部分时) 自动执行 mounted 钩子

代码示例:(当页面加载时,让元素获取焦点)

main.js文件:

javascript 复制代码
app.directive('focus', {
  mounted(el) {
    console.log(el) // 拿到input元素
    el.focus()
  }
})

App.vue文件:

javascript 复制代码
<script setup></script>

<template>
  <div class="app">
    <input type="text" v-focus />
  </div>
</template>

二、什么是指令钩子

上述 mounted 指的是 指令钩子函数 ,和组件的生命周期钩子同名但不是一回事。

  • 组件生命周期钩子 :围绕 组件实例
  • 指令钩子 :围绕 指令绑定的 DOM 元素

常见的指令钩子如下表所示:

阶段 钩子名 说明
绑定 created 指令第一次绑定到元素时调用(元素还没插入 DOM)
挂载 beforeMount 元素即将插入 DOM 时调用
挂载完成 mounted 元素插入 DOM 后调用(常用,比如 el.focus())
更新前 beforeUpdate 元素所在组件更新前调用
更新后 updated 元素所在组件更新后调用
卸载前 beforeUnmount 元素所在组件卸载前调用
卸载后 unmounted 元素卸载后调用

三、指令钩子的参数

一个指令的定义对象可以提供几种钩子函数 (都是可选的):

javascript 复制代码
const myDirective = {
  // 指令第一次绑定到元素时调用(元素还没插入 DOM)
  created(el, binding, vnode) {},
  
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode) {},
  
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode) {},
  
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode) {},
  
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode) {}
}
  1. el:指令当前绑定到的元素。这可以用于直接操作 DOM。
  2. binding:一个对象,包含以下属性:
    1. value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
    2. oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    3. arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    4. modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    5. instance:使用该指令的组件实例。
    6. dir:指令的定义对象。
  3. vnode:代表当前绑定元素的底层 VNode。用于了解绑定的虚拟 DOM 信息,一般用得不多。
  4. prevVnode:代表之前的渲染中指令所绑定元素的 VNode(只在 beforeUpdateupdated 中有用)。

💥注意事项: 除了 el 外,其他参数都是只读的,不要更改它们

📌 举个例子:带参数和修饰符的自定义指令

  • 背景色蓝色(arg = "blue"
  • 加粗(modifiers.bold = true
  • 绑定值 msg 也可以用来动态控制颜色。
javascript 复制代码
app.directive('highlight', {
  mounted(el, binding) {
    console.log(binding)

    // 默认颜色
    let color = 'yellow'

    // 如果传了参数(比如 :blue)
    if (binding.arg) {
      color = binding.arg
    }

    // 如果有修饰符,比如 .bold
    if (binding.modifiers.bold) {
      el.style.fontWeight = 'bold'
    }

    el.style.backgroundColor = color
  }
})

使用:

javascript 复制代码
<p v-highlight:blue.bold="msg">Hello Vue!</p>

四、绑定数据

1. 需求

实现一个 color 指令:传入不同的颜色,给标签设置文字颜色

2. 语法

  1. 在绑定指令时,可以通过 "等号" 的形式为指令绑定具体的参数值

    javascript 复制代码
       <div v-color="colorStr">Some Text</div>
  2. 通过 binding.value 可以拿到指令值,指令值修改会触发 updated 钩子

    javascript 复制代码
       app.directive('指令名', {
         // 挂载后自动触发一次
         mounted(el, binding) { },
         // 数据更新, 每次都会执行
         updated(el, binding) { }
       })

3. 代码示例

main.js文件:

javascript 复制代码
// 
app.directive('color', {
  mounted(el, binding) {
    el.style.color = binding.value
  },
  updated(el, binding) {
    el.style.color = binding.value
  }
})

App.vue文件:

javascript 复制代码
<script setup>
    import { ref } from 'vue'
    const colorStr = ref('red') // 颜色
</script>

<template>
  <p v-color="colorStr"></p>
</template>

4. 简化写法

对于自定义指令来说,一个很常见的情况是仅仅需要在 mountedupdated 上实现相同的行为。这种情况下我们可以直接用一个箭头函数来定义指令,如下所示:

javascript 复制代码
app.directive('color', (el, binding) => {
  // 这会在 mounted 和 updated 时都调用
  el.style.color = binding.value
})

👉 这种写法其实就是 语法糖

Vue 规定: 如果你注册指令时传入的是一个函数,而不是对象,那么它会自动把这个函数同时当作 mountedupdated 两个钩子。

案例:图片懒加载

实际开发过程中,如果项目中图片过多,我们不会一次加载所有的图片,而是当图片出现在可视区的时候才去加载,比如京东、淘宝等都采用了这种方案。

解决方案:封装一个 v-lazyload 自定义指令,实现图片懒加载,从而节省资源、提高性能。

认识 IntersectionObserver

IntersectionObserver 接口(从属于Intersection Observer API)为开发者提供了一种可以异步监听目标元素与其祖先或视窗(viewport)交叉状态的手段。

完整代码

App.vue文件:

javascript 复制代码
<script setup>
    const imgList = [
        'https://img1.baidu.com/it/u=14492133,1259363498&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500',
        'https://img0.baidu.com/it/u=1764531212,1643995922&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500',
        'https://img1.baidu.com/it/u=3461494820,2726880132&fm=253&fmt=auto&app=138&f=JPEG?w=773&h=500',
        'https://img1.baidu.com/it/u=2991964469,2851730176&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500',
        'https://img2.baidu.com/it/u=1519104236,3241953583&fm=253&fmt=auto&app=138&f=JPEG?w=781&h=500',
        'https://img0.baidu.com/it/u=3431675376,3243768390&fm=253&fmt=auto&app=138&f=JPEG?w=712&h=447',
        'https://img1.baidu.com/it/u=2111075854,406597938&fm=253&fmt=auto&app=138&f=JPEG?w=888&h=500',
        'https://img0.baidu.com/it/u=1615464091,2840643412&fm=253&fmt=auto?w=945&h=605',
        'https://img0.baidu.com/it/u=3926979850,631936366&fm=253&fmt=auto&app=120&f=JPEG?w=785&h=500',
        'https://img1.baidu.com/it/u=469866567,781924764&fm=253&fmt=auto&app=120&f=JPEG?w=750&h=500'
    ]
</script>

<template>
    <div class="container">
        <img v-for="item in imgList" v-lazyload="item"
            width="600" height="320" />
    </div>
</template>

<style lang="scss">
* {
    margin: 0;
}
.container {
    width: 600px;
    display: flex;
    flex-direction: column;
    margin: 0 auto;
}
</style>

main.js文件中:

javascript 复制代码
app.directive('lazyload', (el, binding) => {
    const io = new IntersectionObserver(([entry]) => {
        // entry:交叉状态对象
        if(entry.isIntersecting) {
            // 到这说明图片与可视区发送交叉,说明要渲染出来
            el.src = binding.value

            // 监听图片加载错误事件
            el.addEventListener('error', (error) => {
                console.log('图片加载失败', error);
            })

            // 停止监听,关闭监听
            io.unobserve(el)
            io.disconnect()
        }
    })
    // 开启监视
    io.observe(el)
})
相关推荐
坚持学习前端日记10 小时前
Agent AI 多模态交互与全场景架构设计
前端·javascript·人工智能·visual studio
王家视频教程图书馆10 小时前
vue3移动端组件库清单
前端
毕设源码-郭学长10 小时前
【开题答辩全过程】以 基于web的车辆检测管理系统的设计与实现为例,包含答辩的问题和答案
前端
向上的车轮10 小时前
TypeScript 一日速通指南:以订单管理系统实战为核心
前端·javascript·typescript
yqzyy10 小时前
Nginx 配置:alias 和 root 的区别
前端·javascript·nginx
冰糖雪梨dd10 小时前
【JavaScript】 substring()方法详解
开发语言·前端·javascript
John Song10 小时前
npm查看全局安装了哪些命令
前端·npm·node.js
无心水11 小时前
【文档解析】4、跨平台文档解析:JS/Go/C#全攻略
javascript·后端·golang·c#·架构师·大数据分析·分布式系统利器
清汤饺子11 小时前
用了大半年 Claude Code,我总结了 16 个实用技巧
前端·javascript·后端
兆子龙12 小时前
ahooks useMemoizedFn:解决 useCallback 的依赖地狱
java·javascript