Skip to content

VueJS - 指令

VueJS - 自定义指令 (Custom Directives)

Section titled “VueJS - 自定义指令 (Custom Directives)”

指令 (Directives) 是标记(带有 v- 前缀)中的特殊标记,用于告诉库对 DOM 元素执行某些操作。Vue 内置了许多指令(如 v-if, v-for, v-bind, v-model, v-on 等),但您也可以注册自己的自定义指令,以便重用 DOM 操作逻辑。

自定义指令主要用于直接、低级别的 DOM 操作,当您需要封装那些不太适合组件或可复用组合式函数 (composables) 的逻辑时,它们非常有用。

您可以使用 app.directive() 全局注册自定义指令,或在组件的 directives 选项中局部注册,或直接在 <script setup> 中注册。

指令钩子函数 (Directive Hook Functions)

Section titled “指令钩子函数 (Directive Hook Functions)”

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

  • created(el, binding, vnode, prevVnode): 在元素的属性或事件监听器被应用之前调用。
  • beforeMount(el, binding, vnode, prevVnode): 在元素即将被插入 DOM 之前调用。
  • mounted(el, binding, vnode, prevVnode): 在元素被插入 DOM 时调用。通常用于需要元素在场才能进行的设置(例如,添加非 Vue 的事件监听器,初始化第三方库)。
  • beforeUpdate(el, binding, vnode, prevVnode): 在元素自身更新之前调用(例如,由于响应式数据变化)。
  • updated(el, binding, vnode, prevVnode): 在元素及其子组件都更新之后调用。
  • beforeUnmount(el, binding, vnode, prevVnode): 在元素即将从 DOM 中移除之前调用。非常适合执行清理工作(例如,移除事件监听器,销毁库实例)。
  • unmounted(el, binding, vnode, prevVnode): 在元素从 DOM 中移除时调用。
  • el: 指令绑定的 DOM 元素。您可以直接操作它。
  • binding: 一个对象,包含以下属性:
    • value: 传递给指令的值(例如,v-my-directive="theValue")。
    • oldValue: 之前的值(仅在 beforeUpdate 和 updated 中可用)。
    • arg: 传递的参数(例如,v-my-directive:foo)。
    • modifiers: 一个包含修饰符的对象(例如,v-my-directive.bar.baz)。
    • instance: 使用指令的组件实例。
    • dir: 指令定义对象。
  • vnode: 代表元素的底层虚拟节点 (virtual node)。
  • prevVnode: 之前的虚拟节点(仅在 beforeUpdate 和 updated 中可用)。

让我们创建一个简单的 v-focus 指令,当输入元素被挂载时,它会自动聚焦该元素。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Custom Directive Example</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<p>Click the button to mount the input field:</p>
<button @click="showInput = !showInput">Toggle Input</button>
<br><br>
<input type="text" v-if="showInput" v-focus placeholder="I should have focus when I appear">
</div>
<script>
const { createApp, ref } = Vue;
const app = createApp({
setup() {
const showInput = ref(false);
return { showInput };
}
});
// Register the custom directive globally
app.directive('focus', {
// Use 'mounted' hook, as the element needs to be in the DOM to be focused
mounted(el) {
console.log('v-focus mounted on:', el);
el.focus(); // Directly manipulate the DOM element
}
// Note: For components that might re-render, you might also need 'updated'
// if the element could potentially lose focus and needs it reapplied.
});
app.mount('#app');
</script>
</body>
</html>

在此示例中:

  • 我们使用 app.directive() 定义了一个名为 focus 的全局指令。
  • 使用 mounted 钩子,因为聚焦元素需要它存在于 DOM 中。
  • 在 mounted 内部,我们直接调用 el.focus() 来聚焦输入元素。
  • 当输入框使用 v-if 条件渲染并出现在页面上时,v-focus 的 mounted 钩子就会运行,输入框获得焦点。

示例:带有值 (Value) 和参数 (Argument) 的指令

Section titled “示例:带有值 (Value) 和参数 (Argument) 的指令”

让我们创建一个 v-pin 指令,根据参数将元素固定 (position: fixed) 在屏幕一侧,并带有可选的顶部偏移值。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Directive with Value/Arg</title>
<script src="https://unpkg.com/vue@3"></script>
<style>
body { height: 200vh; } /* Ensure scrolling */
.pinned-element {
background-color: lightcoral;
padding: 15px;
border-radius: 5px;
color: white;
}
</style>
</head>
<body>
<div id="app">
<p>Scroll down to see the pinned elements.</p>
<!-- Pin to top, 20px offset -->
<div v-pin:top="20" class="pinned-element">
Pinned to Top (20px offset)
</div>
<!-- Pin to bottom, default 0px offset -->
<div v-pin:bottom class="pinned-element" style="background-color: lightseagreen;">
Pinned to Bottom (default offset)
</div>
</div>
<script>
const { createApp } = Vue;
const app = createApp({});
app.directive('pin', {
mounted(el, binding) {
console.log('v-pin mounted. Binding:', binding);
el.style.position = 'fixed';
// Get the argument (e.g., 'top' or 'bottom')
const side = binding.arg || 'top'; // Default to 'top' if no arg
// Get the value (the offset)
const offset = typeof binding.value === 'number' ? binding.value : 0; // Default to 0 if no value or not a number
// Apply style based on argument and value
el.style[side] = offset + 'px';
// Set opposite side based on the primary side to avoid width issues
if (side === 'top' || side === 'bottom') {
el.style.left = '10px'; // Add some horizontal positioning
el.style.right = '10px';
} else { // Could extend for left/right pinning too
el.style.top = '10px';
}
},
// Optional: Handle updates if the value/argument could change reactively
updated(el, binding) {
if (binding.value !== binding.oldValue || binding.arg !== binding.oldArg) {
console.log('v-pin updated. Binding:', binding);
const side = binding.arg || 'top';
const offset = typeof binding.value === 'number' ? binding.value : 0;
el.style[side] = offset + 'px';
}
}
});
app.mount('#app');
</script>
</body>
</html>

这里:

  • v-pin 指令使用了 binding 对象。
  • binding.arg 提供了参数('top' 或 'bottom')。
  • binding.value 提供了传递的值(第一个 div 是 20,第二个是 undefined)。
  • 我们根据参数和值应用 position: fixed 并设置相应的样式(top 或 bottom)。
  • 添加了 updated 钩子来处理偏移值或参数可能发生的响应式变化(尽管在此简单示例中未演示响应式数据)。

您可以使用 <script setup> 轻松地在组件内部局部定义指令。任何以 v 开头的驼峰式命名 (camelCase) 变量都会被视为自定义指令。

<script setup>
import { ref } from 'vue';
// Directive definition (must start with 'v')
const vHighlight = {
mounted: (el, binding) => {
el.style.backgroundColor = binding.value || 'yellow'; // Default to yellow
},
updated: (el, binding) => {
if (binding.value !== binding.oldValue) {
el.style.backgroundColor = binding.value || 'yellow';
}
}
};
const highlightColor = ref('lightgreen');
</script>
<template>
<p v-highlight="highlightColor">This text will be highlighted.</p>
<p v-highlight>This uses the default highlight color.</p>
<button @click="highlightColor = 'lightblue'">Change Highlight</button>
</template>

这种方法将指令逻辑与使用它的组件 colocated(放在一起),对于不打算全局重用的指令,可以改善组织结构。

关于过滤器 (Filters) 的说明(在 Vue 3 中已移除)

Section titled “关于过滤器 (Filters) 的说明(在 Vue 3 中已移除)”

Vue 2 有一个称为“过滤器”的功能,用于在模板表达式中进行常见的文本格式化(例如,{{ message | capitalize }})。过滤器已从 Vue 3 中移除。

在 Vue 3 中,推荐的过滤器替代方案是:

  • 计算属性 (Computed Properties): 用于基于响应式数据的转换。
  • 方法 (Methods): 用于更复杂的格式化,或需要在事件处理程序或其他方法中直接进行的格式化。

示例:替换一个 ‘capitalize’ 过滤器

Section titled “示例:替换一个 ‘capitalize’ 过滤器”
<script setup>
import { ref, computed } from 'vue';
const message = ref('hello vue 3');
// Option 1: Computed Property
const capitalizedMessage = computed(() => {
if (!message.value) return '';
const firstChar = message.value.charAt(0).toUpperCase();
const rest = message.value.slice(1);
return firstChar + rest;
});
// Option 2: Method
function formatAsCurrency(value) {
if (typeof value !== 'number') return value;
return '$' + value.toFixed(2);
}
const price = ref(29.99);
</script>
<template>
<input v-model="message" />
<p>Original: {{ message }}</p>
<!-- Using computed property -->
<p>Capitalized (Computed): {{ capitalizedMessage }}</p>
<!-- Using method -->
<p>Price: {{ formatAsCurrency(price) }}</p>
</template>

使用计算属性或方法提供了与过滤器相同的功能,但与标准 JavaScript 和 Vue 的响应式系统集成得更自然。

延伸阅读:Vue 官方关于自定义指令的文档:https://vuejs.org/guide/reusability/custom-directives.html