Composables: قلب Vue 3
إذا كنت لا تزال تضع كل منطقك في setup() أو تستخدم Options API للمكونات المعقدة، فإن composables ستغير حياتك. هي ببساطة دوال تغلّف المنطق التفاعلي ويمكن مشاركتها بين المكونات.
// composables/useDebounce.js
import { ref, watch } from 'vue'
export function useDebounce(value, delay = 300) {
const debouncedValue = ref(value.value)
let timeout
watch(value, (newValue) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
debouncedValue.value = newValue
}, delay)
})
return debouncedValue
}
// Usage in a component
const search = ref('')
const debouncedSearch = useDebounce(search, 500)
watch(debouncedSearch, (value) => {
fetchResults(value)
})
مثال عملي أكثر: جلب البيانات من API
// composables/useApi.js
import { ref, shallowRef } from 'vue'
export function useApi() {
const data = shallowRef(null)
const error = ref(null)
const loading = ref(false)
async function execute(url, options = {}) {
loading.value = true
error.value = null
try {
const response = await fetch(url, {
headers: { 'Content-Type': 'application/json', ...options.headers },
...options,
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
return data.value
} catch (e) {
error.value = e.message
throw e
} finally {
loading.value = false
}
}
return { data, error, loading, execute }
}
// Usage
const { data: users, loading, execute: fetchUsers } = useApi()
await fetchUsers('/api/users')
Provide/Inject: تخلّص من تمرير الخصائص عبر المستويات
عندما تحتاج إلى تمرير بيانات عبر عدة مستويات من المكونات، فإن provide و inject أنظف بكثير من تمرير الخصائص (prop drilling) عبر كل مستوى.
// In a parent component
import { provide, ref } from 'vue'
const currentTheme = ref('dark')
const toggleTheme = () => {
currentTheme.value = currentTheme.value === 'dark' ? 'light' : 'dark'
}
provide('theme', { currentTheme, toggleTheme })
// In any descendant (no matter how deep)
import { inject } from 'vue'
const { currentTheme, toggleTheme } = inject('theme')
v-model مع المكونات: الطريقة الصحيحة
ماكرو defineModel في Vue 3 (الإصدار 3.4 وما بعد) يجعل دعم v-model المخصص أمرًا بسيطًا للغاية:
<!-- BaseInput.vue -->
<script setup>
const model = defineModel()
const props = defineProps({
label: String,
type: { type: String, default: 'text' },
})
</script>
<template>
<div class="form-group">
<label>{{ label }}</label>
<input :type="type" v-model="model" class="input" />
</div>
</template>
<!-- Usage -->
<BaseInput v-model="form.email" label="Email" type="email" />
الأداء: التحميل الكسول للمسارات
لا تقم بتحميل تطبيقك بالكامل دفعة واحدة. قسّمه حسب المسارات:
// router/index.js
const routes = [
{
path: '/',
component: () => import('./views/Home.vue'),
},
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue'),
// Only loads when user navigates to /dashboard
},
{
path: '/settings',
component: () => import('./views/Settings.vue'),
},
]
المراقبون: استخدم watchEffect للحالات البسيطة
// Instead of manually listing dependencies...
watch([firstName, lastName], ([first, last]) => {
fullName.value = `${first} ${last}`
})
// ...let Vue track them automatically
watchEffect(() => {
fullName.value = `${firstName.value} ${lastName.value}`
})
Template Refs مع TypeScript
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value?.focus()
})
</script>
<template>
<input ref="inputRef" />
</template>
ملخص سريع للنصائح
- استخدم
shallowRefللكائنات الكبيرة التي لا تحتاج إلى تفاعلية عميقة — فهو أسرع بشكل ملحوظ - فضّل
computedعلى الدوال في القوالب — القيم المحسوبة يتم تخزينها مؤقتًا - استخدم
toRefsعند تفكيك الكائنات التفاعلية للحفاظ على التفاعلية - أضف خاصية
keyلحلقاتv-forبمعرّفات فريدة، وليس فهارس المصفوفة - استخدم
<Suspense>مع المكونات غير المتزامنة لحالات التحميل - حافظ على حجم المكونات تحت 200 سطر — إذا كانت أطول، استخرج composable أو مكوّنًا فرعيًا
أفضل كود Vue يُقرأ كوصف لما تفعله الواجهة، لا كيف تفعله. الـ composables تتولى "الكيف"، والمكونات تصف "الماذا".