Vue.js: التوجيهات المخصصة
آخر تحديث: 2026-08-26
تتيح لك التوجيهات المخصصة توسيع نطاق صيغة template في Vue — باستخدام سمات خاصة تبدأ بـ v- للتعامل مباشرةً مع DOM الأساسي. يأتي Vue مزودًا بتوجيهات مدمجة مثل v-if وv-for وv-model؛ ويمكنك إنشاء توجيهات خاصة بك مثل v-focus وv-permission وv-debounce.
تُعد التوجيهات المخصصة وسيلة فعالة لكتابة «أدوات DOM من المستوى المنخفض» — فهي تُغلف عمليات DOM القابلة لإعادة الاستخدام في صيغة إعلانية. ويتيح لك فهم «نقاط ربط دورة الحياة» الخمس الرئيسية كتابة جميع أنواع التوجيهات v.
1. ما ستتعلمه
- جوهر الأوامر المخصصة وحالات الاستخدام الثلاث الرئيسية لها
- القيادة العالمية
app.directive() - التعليم المحلي
directives: {} - 5 نقاط ربط لدورة الحياة (created/beforeMount/mounted/beforeUpdate/unmounted)
- 5 توجيهات عملية (v-focus / v-permission / v-debounce / v-copy / v-lazy-load)
- معلمات الأوامر، والمعدِّلات، والقيم
- 5 أنماط خاطئة (الإساءة في الاستخدام، وتجاوز الدوال المدمجة، ونسيان إزالة البيانات، وما إلى ذلك)
2. كابوس زر «الإذن» «المكرر في 5 أماكن»
(1) المشكلة: تتطلب جميع الأزرار الخمسة إجراء فحوصات للأذونات
كان لدى المسؤول عن حساب أليس 5 أزرار تتطلب التحقق من الصلاحيات:
<!-- ❌ The "Broken" Version:5 a button,5 Permission Code -->
<template>
<button v-if="hasPermission('user.create')" @click="createUser">Create</button>
<button v-if="hasPermission('user.delete')" @click="deleteUser">Delete</button>
<button v-if="hasPermission('user.edit')" @click="editUser">Edit</button>
<button v-if="hasPermission('order.create')" @click="createOrder">Create Order</button>
<button v-if="hasPermission('order.cancel')" @click="cancelOrder">Cancel</button>
</template>
<script setup>
function hasPermission(perm) {
return user.value.permissions?.includes(perm)
}
</script>
5 أزرار × 5 عمليات التحقق من الأذونات = 25 سطراً من التعليمات البرمجية المتكررة. تتطلب إضافة زر جديد كتابة v-if آخر.
مدير المنتج تشارلي:
"أليس، لدينا أكثر من 50 زرًا في لوحة الإدارة. نحتاج إلى توجيه 'v-permission' لجعل الأمر أكثر تنظيمًا."
(2) الحل باستخدام توجيه Vue مخصص: 1 v-permission
// directives/permission.js
export const permission = {
mounted(el, binding) {
const { value } = binding // 'user.create'
const userPermissions = getCurrentUser().permissions || []
if (!userPermissions.includes(value)) {
el.parentNode?.removeChild(el) // If you don't have permission, remove it.
}
}
}
// main.js
import { permission } from './directives/permission'
app.directive('permission', permission)
<!-- Usage: 1 v-permission replaces all v-if -->
<template>
<button v-permission="'user.create'" @click="createUser">Create</button>
<button v-permission="'user.delete'" @click="deleteUser">Delete</button>
<button v-permission="'order.create'" @click="createOrder">Create Order</button>
</template>
50 زرًا، 50 v-permission — أكثر إيجازًا بنسبة 50% من 50 v-if.
(3) الإيرادات
بعد التوجيهات المخصصة:
- حجم الكود: 25 سطراً من v-if → 3 أسطر من v-permission (-88%)
- زر جديد: 1 v-permission تحل محل 1 v-if
- منطق الأذونات المركزي: ملف directives/permission.js واحد
- قابل لإعادة الاستخدام: يمكن أيضًا استخدام v-permission في مشاريع أخرى
3. أساسيات الأوامر المخصصة
(1) 3 طرق للتسجيل
// 1. Global Commands(main.js)
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
// Global Registration:All components are available v-focus
app.directive('focus', {
mounted(el) {
el.focus()
}
})
app.mount('#app')
<!-- Local Instructions(Recommendations) -->
<!-- src/components/Input.vue -->
<script setup>
// Local Registration:Only this component works
const vFocus = {
mounted(el) {
el.focus()
}
}
</script>
<template>
<input v-focus>
</template>
// 2. Abbreviation(mounted + updated)
app.directive('color', (el, binding) => {
el.style.color = binding.value
})
(2) النقاط الخمس الرئيسية في دورة الحياة
app.directive('demo', {
// 1. created(Command Creation)
created(el, binding) {
console.log('1. Command Creation')
},
// 2. beforeMount(Before mounting the element)
beforeMount(el) {
console.log('2. Before Mounting')
},
// 3. mounted(The element has been mounted)⭐ Most Commonly Used
mounted(el, binding) {
console.log('3. Mounted')
},
// 4. beforeUpdate(Before the dependency update)
beforeUpdate(el, binding) {
console.log('4. Before the update')
},
// 5. updated(After the dependency update)
updated(el, binding) {
console.log('5. Updated')
},
// 6. beforeUnmount(Before Uninstalling)
beforeUnmount(el) {
console.log('6. Before Uninstalling')
},
// 7. unmounted(After uninstallation)⭐ For cleaning
unmounted(el) {
console.log('7. Uninstalled')
}
})
(3) شرح مفصل لمعلمات Hook
// el, binding, vnode, prevVnode 4 parameter
mounted(el, binding, vnode, prevVnode) {
// el: Elements Bound to Commands
el.style.color = 'red'
// binding: Instruction Information Object
binding.value // Instruction Value, e.g. v-foo="bar" → bar
binding.arg // Parameters, e.g. v-foo:arg → 'arg'
binding.modifiers // Modifiers, e.g. v-foo.bar → { bar: true }
binding.instance // Component Instances That Use Commands
binding.dir // Instruction-Defined Objects
// vnode: Vue Virtual Node(Generally not used)
// prevVnode: Previous Virtual Node
}
4. 5 أوامر أساسية للاستخدام العملي
(1) v-focus: التركيز التلقائي
// directives/focus.js
export const focus = {
mounted(el, binding) {
if (binding.value !== false) {
el.focus()
}
}
}
<template>
<!-- 1. Autofocus -->
<input v-focus>
<!-- 2. Focus on Conditions -->
<input v-focus="shouldFocus">
<!-- 3. Delayed Focus -->
<input v-focus:delay="500">
</template>
(2) v-permission: التحكم في الأذونات
// directives/permission.js
import { getCurrentUser } from '@/utils/auth'
export const permission = {
mounted(el, binding) {
const { value, modifiers } = binding
const user = getCurrentUser()
// value: String 'user.create' or Array ['user.create', 'user.delete']
// modifiers.disable: Disable, not remove
const required = Array.isArray(value) ? value : [value]
const hasPermission = required.every(p =>
user.permissions?.includes(p)
)
if (!hasPermission) {
if (modifiers.disable) {
el.disabled = true
el.title = 'No permission'
} else {
el.parentNode?.removeChild(el)
}
}
}
}
<template>
<!-- Single Permission -->
<button v-permission="'user.create'">Create</button>
<!-- Multiple Permissions(All met)-->
<button v-permission="['user.read', 'user.write']">Edit</button>
<!-- Disable when permissions are lacking(rather than removing)-->
<button v-permission.disable="'user.delete'">Delete</button>
</template>
(3) v-debounce: منع الارتداد في الأحداث
// directives/debounce.js
export const debounce = {
mounted(el, binding) {
const { value, arg = 300 } = binding
if (typeof value !== 'function') {
console.warn('v-debounce: value must be a function')
return
}
let timer = null
el.__debounceTimer__ = timer
el.addEventListener('click', () => {
clearTimeout(timer)
timer = setTimeout(() => value(), arg)
el.__debounceTimer__ = timer
})
},
unmounted(el) {
if (el.__debounceTimer__) {
clearTimeout(el.__debounceTimer__)
}
}
}
<template>
<button v-debounce="handleClick" v-debounce:500="handleClick">Click me</button>
<input v-debounce="handleInput" v-debounce:1000="handleInput">
</template>
<script setup>
function handleClick() {
console.log('Clicked (debounced 500ms)')
}
</script>
(4) v-copy: انقر للنسخ
// directives/copy.js
export const copy = {
mounted(el, binding) {
el.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(binding.value)
const original = el.textContent
el.textContent = 'Copied!'
setTimeout(() => { el.textContent = original }, 1500)
} catch (err) {
console.error('Copy failed:', err)
}
})
}
}
<template>
<button v-copy="shareUrl">Copy Link</button>
<code v-copy="apiKey">Click to copy</code>
</template>
(5) v-lazy-load: التحميل المؤجل للصور
// directives/lazyLoad.js
export const lazyLoad = {
mounted(el, binding) {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) {
el.src = binding.value
observer.unobserve(el)
}
})
observer.observe(el)
el.__observer__ = observer
},
unmounted(el) {
el.__observer__?.disconnect()
}
}
<template>
<img v-lazy-load="imageUrl" alt="...">
</template>
5. معلمات الأوامر ومعدِّلاتها وقيمها
(1) ثلاثة أنواع من الأوامر
<!-- 1. v-directive="value" (value) -->
<input v-foo="username">
<!-- 2. v-directive:arg(Parameters,Fixed String) -->
<input v-foo:delay="500">
<!-- 3. v-directive.modifier(Modifiers,Boolean objects) -->
<input v-foo.bar>
<!-- 4. Combination -->
<input v-foo:delay.bar="500">
(2) طرق استقبال JS
app.directive('demo', (el, binding) => {
// v-demo="123"
binding.value // 123
// v-demo:abc
binding.arg // 'abc'
// v-demo.foo
binding.modifiers // { foo: true }
// v-demo:abc.foo="123"
binding.value // 123
binding.arg // 'abc'
binding.modifiers // { foo: true }
})
(3) 5 سيناريوهات تركيبية رئيسية
<!-- Scene 1:v-permission:disable -->
<button v-permission:disable="'user.create'">
<!-- arg='disable', value='user.create' -->
</button>
<!-- Scene 2:v-debounce:500 -->
<button v-debounce:500="handler">
<!-- arg='500'(500ms Image Stabilization) -->
</button>
<!-- Scene 3:v-once.lazy -->
<img v-once.lazy="imageUrl">
<!-- modifiers.lazy=true, value=imageUrl -->
</template>
6. مثال كامل: 5 أوامر رئيسية + تدريب عملي
▶ مثال: 1. التنفيذ الكامل لـ v-focus
export const focus = {
mounted(el, binding) {
if (binding.value === false) return
if (binding.arg) {
setTimeout(() => el.focus(), parseInt(binding.arg))
} else {
el.focus()
}
}
}
▶ مثال: 2. التنفيذ الكامل لـ v-permission
import { getCurrentUser } from '@/utils/auth'
export const permission = {
mounted(el, binding) {
const { value, modifiers } = binding
const user = getCurrentUser()
const required = Array.isArray(value) ? value : [value]
const ok = required.every(p => user.permissions?.includes(p))
if (!ok) {
if (modifiers.disable) {
el.disabled = true
el.style.opacity = '0.5'
el.title = 'No permission'
} else {
el.parentNode?.removeChild(el)
}
}
},
updated(el, binding) {
// Permissions may change(User Role Switching),Re-examine
this.mounted(el, binding)
}
}
▶ مثال: 3. التنفيذ الكامل لـ v-debounce
export const debounce = {
mounted(el, binding) {
const fn = binding.value
const delay = parseInt(binding.arg) || 300
if (typeof fn !== 'function') {
console.warn('[v-debounce] value must be a function')
return
}
let timer = null
el.addEventListener('click', () => {
clearTimeout(timer)
timer = setTimeout(() => fn(), delay)
})
el._debounceTimer = timer
},
unmounted(el) {
if (el._debounceTimer) clearTimeout(el._debounceTimer)
}
}
▶ مثال: 4. مرجع سريع لـ 5 أخطاء شائعة
| الخطأ | الأعراض | الحل |
|---|---|---|
| اسم التوجيه بدون "v-" | لا يعمل | v-focus (وليس focus) |
| القيمة ليست دالة | لا يتم التنفيذ | تحقق من v-debounce="handler" |
| تنظيف الموارد غير المثبتة | تسربات الذاكرة | تنظيف المؤقتات/المراقبين |
| تجاوز التوجيهات المدمجة في Vue | خطأ | الاسم غير "v-if"، إلخ |
| لا يتم التعرف على تغييرات قيم الأوامر | تم التثبيت مرة واحدة فقط | استخدام خطاف updated |
▶ مثال: 5. مقارنة بين مؤشرات الأداء الخمسة الرئيسية
| التنفيذ | قابلية إعادة الاستخدام | الأداء | قابلية التطبيق |
|---|---|---|---|
v-if + وظائف مساعدة |
❌ | ⭐⭐⭐ | لمرة واحدة |
| الأوامر المخصصة | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | أدوات DOM |
| المكونات العالمية | ⭐⭐⭐ | ⭐⭐⭐ | واجهة مستخدم معقدة |
| قابل للتركيب | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | منطق الأعمال |
| بينيا | ⭐⭐⭐⭐ | ⭐⭐⭐ | الوضع العالمي |
▶ مثال: 6. 5 نصائح لاستخدام الأوامر المدمجة
// Draw on Vue Implementation of Built-in Instructions
import { createApp } from 'vue'
const app = createApp({})
// 1. v-show(Control display)
app.directive('show', {
mounted(el, binding) { el.style.display = binding.value ? '' : 'none' },
updated(el, binding) { el.style.display = binding.value ? '' : 'none' }
})
// 2. v-text(Settings textContent)
app.directive('text', {
mounted(el, binding) { el.textContent = binding.value },
updated(el, binding) { el.textContent = binding.value }
})
// 3. v-html(Settings innerHTML,XSS Risks)
app.directive('html', {
mounted(el, binding) { el.innerHTML = binding.value },
updated(el, binding) { el.innerHTML = binding.value }
})
// 4. v-once(Render only once)
app.directive('once', {
mounted(el, binding, vnode) {
if (binding.value !== indefinido) {
vnode.context[binding.arg] = binding.value
}
}
})
❓ أسئلة شائعة
value الخاص بأحد التوجيهات تفاعلي؟value إلى تشغيل المرفق updated. للاستماع إلى التغييرات التي تطرأ على value، استخدم watch(binding.value) أو قارن بين القيم القديمة والجديدة داخل المرفق updated.v-permission:disable؟arg='disable'، modifiers.disable=true. تحدد التوجيهية binding.modifiers.disable ما إذا كان سيتم تعطيلها أو إزالتها.defineDirective والأنواع العامة في TypeScript. ومع ذلك، عادةً ما يكون استخدام كائنات JavaScript أبسط.<input v-my-directive="myRef" />، حيث يمثل binding.value في الأمر كائن المرجع. ومع ذلك، لا يُنصح بذلك (نمط غير مرغوب فيه).vFocus / vClickOutside / vLazyLoad / vInfiniteScroll وغيرها هي جميعها توجيهات توفرها مكتبة VueUse. من الأفضل استخدام مكتبة VueUse بدلاً من كتابة توجيهات خاصة بك.📖 ملخص
- التوجيهات المخصصة هي سمات خاصة تبدأ بحرف «v-» وتُستخدم للتعامل مباشرةً مع نموذج DOM
- 3 أنواع من التسجيل: عالمي (app.directive) / محلي (directives: {}) / مختصر (يجمع بين التسجيل والتحديث)
- 5 نقاط ربط لدورة الحياة: created / beforeMount / mounted / beforeUpdate / updated / beforeUnmount / unmounted
- 5 تطبيقات عملية: v-focus / v-permission / v-debounce / v-copy / v-lazy-load
- 3 أشكال: القيمة / الحجة (arg) / المُعدِّل (المُعدِّلات)
- 5 أنماط خاطئة: نسيان البادئة
v-/valueغير الوظيفي / نسيان إجراء التنظيف / تجاوز القيم المدمجة / عدم الاستجابة لتغييراتvalue
📝 تمارين
-
أسئلة أساسية (مستوى الصعوبة: ⭐)
تنفيذ التوجيه
v-focus:- يقبل المعلمة
arg(التأخير بالمللي ثانية) - يقبل المُعدِّلات (prevent: يمنع السلوك الافتراضي)
- يقبل المعلمة
-
مسائل متقدمة (مستوى الصعوبة: ⭐⭐)
تنفيذ النسخة الكاملة من v-permission:
- يدعم سلاسل الأذونات الفردية: v-permission="'user.create'"
- يدعم مصفوفات الأذونات: v-permission="['user.read', 'user.write']"
- المُعدِّل المدعوم: .disable (يعطل بدلاً من الإزالة)
- باستخدام currentUser (توفير/إدخال Injection)
-
مسألة التحدي (مستوى الصعوبة: ⭐⭐⭐)
تنفيذ «مجموعة أوامر» كاملة:
- 5 توجيهات: v-focus / v-permission / v-debounce / v-copy / v-lazy-load
- التنفيذ الكامل لكل تعليمات + أنواع TypeScript
- تصدير كل شيء من
directives/index.js - 5 حالات اختبار (باستخدام Vitest)
- الاستخدام في سيناريوهات واقعية (5 سيناريوهات مختلفة في البنية الخلفية للتجارة الإلكترونية)
- توثيق واجهة برمجة التطبيقات (API) والمعلمات الخاصة بكل أمر