Skip to content

如何实现一个v-*指令

在vue.js中,我们经常使用v-for、v-if、v-bind、v-model等指令。

指令是如何实现的?

指令基本概念

Vue指令是一种特殊的标记,用于在模板中对DOM元素进行底层操作。指令以 v- 前缀标识,如 v-ifv-showv-bind 等。

指令实现原理

  1. 编译阶段:Vue模板编译器会识别模板中的指令语法
  2. 解析阶段:将指令表达式解析为对应的处理函数
  3. 生命周期绑定:将指令逻辑绑定到组件的生命周期中
  4. DOM操作:在适当的时机对DOM元素进行操作

指令核心机制

  • 指令本质上是对DOM操作的封装
  • 通过生命周期钩子函数控制指令的行为时机
  • 支持动态参数、修饰符和表达式求值

自定义指令实现要素

  • 遵循Vue指令的生命周期规范
  • 正确处理指令参数和修饰符
  • 在合适的时机进行DOM操作和清理工作

实现一个v-loading指令

功能:需传递一个boolean值,当值为true时,显示loading效果,false则隐藏。

  1. 创建一个对象,用于存放指令逻辑。
ts
const vLoading = {}
  1. 指令一般操作dom元素,所以要在页面挂载后执行。
ts
const vLoading = {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
        //     el为指令绑定的元素,binding为指令的参数和修饰符
        const loadingEl = document.createElement('div');
        loadingEl.className = 'loading';
        loadingEl.innerText = '加载中...';

        el.style.position = 'relative';

        loadingEl.style = `
      position: absolute;
      inset:0;
      color:#fff;
      display:flex;
      justify-content: center;
      align-items: center;
      background-color: rgba(0,0,0,0.5);
    `;

        if (binding.value) {
            el.appendChild(loadingEl);
        } else {
            el.removeChild(loadingEl);
        }
    }
}
  1. 状态,当依赖数据更新时,指令也要更新,否则指令就无意义
ts
const vLoading = {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
        const loadingEl = document.createElement('div');
        loadingEl.className = 'loading';
        loadingEl.setAttribute("data-loading", "true")
        loadingEl.innerText = '加载中...';

        el.style.position = 'relative';

        loadingEl.style = `
      position: absolute;
      inset:0;
      color:#fff;
      display:flex;
      justify-content: center;
      align-items: center;
      background-color: rgba(0,0,0,0.5);
    `;

        if (binding.value) {
            el.appendChild(loadingEl);
        } else {
            el.removeChild(loadingEl);
        }
    },
    updated(el: HTMLElement, binding: DirectiveBinding) {
        if (binding.value === binding.oldValue) return
        const loadingEl = document.querySelector('.loading')!;
        if (binding.value) {
            el.appendChild(loadingEl);
        } else {
            el.removeChild(loadingEl);
        }
    }

}

完整测试代码

vue

<script setup lang="ts">
  import {type DirectiveBinding, ref} from 'vue';

  const loading = ref(true);

  const vLoading = {
    mounted(el: HTMLElement, binding: DirectiveBinding) {
      const loadingEl = document.createElement('div');
      loadingEl.className = 'loading';
      loadingEl.innerText = '加载中...';

      el.style.position = 'relative';

      loadingEl.style = `
      position: absolute;
      inset:0;
      color:#fff;
      display:flex;
      justify-content: center;
      align-items: center;
      background-color: rgba(0,0,0,0.5);
    `;

      if (binding.value) {
        el.appendChild(loadingEl);
      } else {
        el.removeChild(loadingEl);
      }
    },
    updated(el: HTMLElement, binding: DirectiveBinding) {
      if (binding.value === binding.oldValue) return
      const loadingEl = document.querySelector('.loading')!;
      if (binding.value) {
        el.appendChild(loadingEl);
      } else {
        el.removeChild(loadingEl);
      }
    }

  }

  setTimeout(() => {
    loading.value = false;
  }, 3000)

</script>

<template>

  <div class="box" v-loading="loading"></div>

</template>

<style scoped>
  body {
    width: 100vw;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;

  }

  .box {
    width: 400px;
    height: 400px;
    background-color: red;
  }


</style>

全局使用

在main.ts中全局注册

ts
const vLoading: Directive<HTMLElement, boolean> = {
    mounted(el, binding) { /* 同上 */
    },
    updated(el, binding) { /* 同上 */
    }
};

app.directive('loading', vLoading);

钩子函数

directive也是有钩子函数的,可在特定时期执行操作。

钩子函数作用
beforeMount在指令首次绑定到元素且未挂载到DOM之前调用
mounted被绑定元素挂载到DOM之时调用,可访问元素实例,但DOM节点还未更新
beforeUpdate在指令所在组件的 VNode 更新之前调用
updated在指令所在组件的 VNode 及其子组件的 VNode 更新之后调用
beforeUnmount在指令所绑定的元素/组件/父组件被卸载之前调用
unmounted在指令所绑定的元素/组件/父组件被卸载之时调用
  • VNode:Vue中的虚拟DOM对象,用于描述真实DOM结构。

binding对象所有属性

属性描述
instance指令所绑定的组件实例
value指令的绑定值
oldValue指令绑定的值的旧值
arg指令的参数,如v-bind:arg
modifiers指令的修饰符,如.stop

这是我的个人文档