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) 的逻辑时,它们非常有用。
创建自定义指令
Section titled “创建自定义指令”您可以使用 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 中移除时调用。
钩子参数 (Hook Arguments)
Section titled “钩子参数 (Hook Arguments)”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 指令
Section titled “示例:自定义 v-focus 指令”让我们创建一个简单的 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钩子来处理偏移值或参数可能发生的响应式变化(尽管在此简单示例中未演示响应式数据)。
局部指令 (Section titled “局部指令 ()”
您可以使用 <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 Propertyconst capitalizedMessage = computed(() => { if (!message.value) return ''; const firstChar = message.value.charAt(0).toUpperCase(); const rest = message.value.slice(1); return firstChar + rest;});
// Option 2: Methodfunction 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