【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)
})
相关推荐
共享家952713 小时前
搭建 AI 聊天机器人:”我的人生我做主“
前端·javascript·css·python·pycharm·html·状态模式
Halo_tjn14 小时前
基于封装的专项 知识点
java·前端·python·算法
摘星编程15 小时前
OpenHarmony环境下React Native:自定义useTruncate文本截断
javascript·react native·react.js
Duang007_15 小时前
【LeetCodeHot100 超详细Agent启发版本】字母异位词分组 (Group Anagrams)
开发语言·javascript·人工智能·python
有来技术15 小时前
Spring Boot 4 + Vue3 企业级多租户 SaaS:从共享 Schema 架构到商业化套餐设计
java·vue.js·spring boot·后端
东东51616 小时前
学院个人信息管理系统 (springboot+vue)
vue.js·spring boot·后端·个人开发·毕设
2601_9498683616 小时前
Flutter for OpenHarmony 电子合同签署App实战 - 主入口实现
开发语言·javascript·flutter
m0_7482299917 小时前
Vue2 vs Vue3:核心差异全解析
前端·javascript·vue.js
C澒17 小时前
前端监控系统的最佳实践
前端·安全·运维开发