手把手教你如何打造自己的Vue自定义指令库

666 阅读6分钟

1.前言

在Vue中,指令是一个特殊的属性。Vue会根据不同指令,在背后执行不同的操作。

2.内置指令

截止到Vue3.2版本,Vue一共有16个内置指令,分别是:

  • 1.v-text:用于更新元素的 textContent
<div v-text="'<h2>v-text</h2>'"></div>

# 输出 <h2>v-text</h2>
  • 2.v-html:与v-text很像,只是v-html用于更新元素的 innerHTML
<div v-html="'<h2>v-html</h2>'"></div>

# 输出 v-html
# h2被渲染为了元素
  • 3.v-show:可以根据表达式的真假值,切换元素的display: block | none;值,用于控制元素的展示和隐藏
<div v-show="isShow">show</div>

<!-- 注意:v-show 不支持 <template> 元素,也不支持 v-else -->
  • 4.v-if:用于根据表达式的真假值来有条件地渲染元素。与v-show相比,v-if在切换时是元素的销毁或重建,而不是简单的显示隐藏。因此当进行高频率的显示和隐藏操作时,应该优先使用v-show
<div v-if="isShow">show</div>
  • 5.v-else:和v-if配合使用,当v-if满足条件时展示v-if的元素,否则展示v-else的元素
<div v-if="isShow">show</div>
<div v-else>hidden</div>

<!-- 注意:v-else前一个兄弟元素必须有 v-if 或 v-else-if -->
  • 6.v-else-if:和v-else一样,前一个兄弟元素必须有v-if或 v-else-if
<div v-if="isShow">show</div>
<div v-else-if="isOther">other</div>
<div v-else>hidden</div>
  • 7.v-for:一个用于迭代的指令,可以根据源数据多次渲染元素或模板块
<div v-for="item in [1, 2, 3]" :key="item">
  {{item}}
</div>
  • 8.v-on:用于给元素绑定事件,可以缩写为:@。有如下的修饰符
.stop - 调用 event.stopPropagation()
.prevent - 调用 event.preventDefault()
.capture - 添加事件侦听器时使用 capture 模式
.self - 只当事件是从侦听器绑定的元素本身触发时才触发回调
.{keyAlias} - 仅当事件是从特定键触发时才触发回调,比如键盘enter键
.once - 只触发一次回调
.left - 只当点击鼠标左键时触发
.right - 只当点击鼠标右键时触发
.middle - 只当点击鼠标中键时触发
.passive - { passive: true } 模式添加侦听器

# demo:停止冒泡
<button @click.stop="doThis">Do</button>
  • 9.v-bind:用于绑定数据和元素属性,可以缩写为: 或.(当使用 .prop 修饰符时)。有如下修饰符
.camel - 将 kebab-case attribute 名转换为 camelCase
.prop - 将一个绑定强制设置为一个 DOM property。3.2+
.attr - 将一个绑定强制设置为一个 DOM attribute。3.2+

# demo
<div :someProperty.prop="someObject"></div>
# 相当于
<div .someProperty="someObject"></div>
  • 10.v-model:在表单控件或者组件上可以创建双向绑定,限制于:<input> <select> <textarea> components。有如下修饰符
.lazy - 惰性更新,监听 change 而不是 input 事件
.number - 输入字符串转为有效的数字
.trim - 输入首尾空格过滤

# demo
<input type="text" v-model="demo" />
<input type="text" v-model.lazy="demo" />
<input type="text" v-model.number="demo" />
<input type="text" v-model.trim="demo" />
<h3>{{demo}}</h3>
  • 11.v-slot:用于提供具名插槽或需要接收 prop 的插槽,可选择性传递参数,表示插槽名,默认值default
<!-- 父组件 -->
<div class="parent">
  <child-item>
    <p>slot展示的内容</p>
  </child-item>
</div>

<!-- 子组件 -->
<div class="child">
  <slot></slot>
</div>
  • 12.v-pre:用于跳过这个元素及其子元素的编译过程
<div v-pre>{{demo}}</div>

# 输出:{{demo}}
  • 13.v-cloak:用于解决插值表达式在页面闪烁问题
<div>{{demo}}</div>
<div v-cloak>{{demo}}</div>

<style>
[v-cloak] {
  display: none;
}
</style>
  • 14.v-once:用于表示只渲染一次,当要重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过
  • 15.v-memo:用于缓存一个模板的子树。该指令接收一个固定长度的数组作为依赖值进行记忆比对。如果数组中的每个值都和上次渲染的时候相同,则整个该子树的更新会被跳过
<div v-memo="[valueA, valueB]"></div>

<!-- 在重新渲染时,如果 valueA 与 valueB 都维持不变,-->
<!-- 那么对这个 <div> 以及它的所有子节点的更新都将被跳过 -->
  • 16.v-is:检测动态组件。已在 3.1.0 中废弃,改用:is
<component :is="currentView"></component>

3.自定义指令

让我们先到官网简单看一下说明——vue3自定义指令,下面我们分别来创建自定义局部指令和自定义全局指令,加深对官网知识的理解。

3-1.自定义局部指令

<template>
  <div class="directive">
    <input type="text" v-model="inputValue" />
    <br><br>
    <input type="text" v-focus v-model="inputValue" />
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'Directive',
  // 自定义局部指令
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      },
    },
  },
  setup() {
    const inputValue = ref(null)
    return {
      inputValue
    }
  },
})
</script>


3-2.自定义全局指令

// main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)

app.directive('focus', {
  // 自定义全局指令
  mounted(el) {
    el.focus()
  }
})

app.use(router).mount('#app')
<!-- Home.vue -->
<template>
  <div class="home">
    <input type="text" v-model="inputValue" />
    <br><br>
    <input type="text" v-focus v-model="inputValue" />
  </div>
</template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
  name: 'Home',
  setup() {
    const inputValue = ref(null)
    return {
      inputValue
    }
  }
});
</script>


<!-- Directive.vue -->
<template>
  <div class="directive">
    <input type="text" v-focus v-model="inputValue" />
    <br><br>
    <input type="text" v-model="inputValue" />
  </div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'Directive',
  setup() {
    const inputValue = ref(null)
    return {
      inputValue
    }
  },
})
</script>

3-3.自定义指令参数详解

通过上述例子,我们可以知道无论是使用directive自定义全局指令,还是使用directive自定义局部指令,都需要一个指令名,如focus,并且还需要mounted这类的钩子函数,以及钩子函数中的参数el

下面说一下自定义指令中的钩子函数:

  • 1.created:在绑定元素的 attribute 或事件监听器被应用之前调用
  • 2.beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用
  • 3.mounted:在绑定元素的父组件被挂载后调用
  • 4.beforeUpdate:在更新包含组件的 VNode 之前调用
  • 5.updated:在包含组件的 VNode及其子组件的 VNode更新后调用
  • 6.beforeUnmount:在卸载绑定元素的父组件之前调用
  • 7.unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次

下面再说一下7个钩子函数中接收的4个参数:

  • 1.el:用于直接操作 DOM,表示指令绑定到的元素
  • 2.vnode:虚拟DOM,一个真实 DOM 元素的蓝图,对应el
  • 3.prevNode:上一个虚拟节点
  • 4.binding对象:包含以下6个属性
instance:使用指令的组件实例
value:传递给指令的值
oldValue:先前的值
arg:传递给指令的参数
modifiers:传递给指令的修饰符
dir:一个对象,其实就是注册指令时传递的配置对象



3-4.自定义指令库的创建

关于自定义组件的相关知识已经全部介绍完毕,下面就让我们来创建一个属于自己的自定义指令库

  • 1.创建一个盒子位置固定的指令:src/directives/position.js
const pos = {
  mounted(el, binding) {
    // 传递给指令的值:盒子是否要固定
    let pinned = binding.value
    // 传递给指令的修饰符:表示盒子定在哪里
    let position = binding.modifiers
    // 传递给指令的参数:可以表示盒子的特性
    let args = binding.arg
    
    if (pinned) {
      el.style.position = 'fixed'
      if (args == 'warning') {
        el.style.backgroundColor = 'pink'
      } else {
        el.style.backgroundColor = 'gray'
      }
      for (let val in position) {
        if (position[val]) {
          el.style[val] = '10px'
        }
      }
    } else {
      el.style.position = 'static'
      el.style.backgroundColor = ''
    }
  },
}
export default pos
  • 2.创建自定义指令统一管理文件:src/directives/index.js
import pos from './position'
const directives = {
  pos,
}
export default {
  install(app) {
    Object.keys(directives).forEach((key) => {
      app.directive(key, directives[key])
    })
  },
}

  • 3.批量注册自定义指令:src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Directives from './directives/index.js'
const app = createApp(App)

app.use(Directives)
app.use(router).mount('#app')
  • 4.调用全局自定义指令:src/views/Directive.vue
<template>
  <div class="directive">
    <div class="box" v-pos:warning.right.top="true">{{msg}}</div>
    <div class="box" v-pos:normal.right.bottom="true">{{msg}}</div>
    <div class="box" v-pos:normal.left.bottom="true">{{msg}}</div>
    <div class="box">{{msg}}</div>
  </div>
</template>

<script>
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'Directive',
  setup() {
    const msg = ref('message')
    return {
      msg
    }
  },
})
</script>

<style scoped>
.box {
  height: 100px;
  width: 100px;
  text-align: center;
  line-height: 100px;
  color: aliceblue;
  background-color: green;
}
</style>

4.尾声

以上便是我对Vue指令知识的整理以及自己的动手实践,目前我的一个项目仓库在此 传送门,有兴趣的请点击。本期分享结束,谢谢~ 希望大家看过此文后都能拥有一套自己的自定义指定库。

5.参考