Vue 3 للقادمين من أطر أخرى: Composition API بمنطق عملي
دليل عملي لـ Composition API في Vue 3 للقادمين من React أو Options API: ref وcomputed وwatch وComposables، بأمثلة كود وربط بكل ما تعرفه من أطر أخرى.
إن كنت قادمًا إلى Vue من React أو Angular أو حتى من النسخة القديمة Options API، فأوّل ما ستصادفه في Vue 3 هو Composition API مع صياغة script setup. وقد يبدو للوهلة الأولى مختلفًا ومربكًا، لكنه في جوهره أبسط ممّا يبدو، وأقرب إلى كتابة جافاسكربت عاديّة ممّا اعتدت. هذا الدليل يأخذك من الصفر بمنطق عملي، مع ربط كل مفهوم بما تعرفه أصلًا من أطر أخرى.
لماذا Composition API أصلًا؟
في الأسلوب القديم (Options API)، كنت تنظّم الكود حسب نوعه: كل البيانات في data، وكل الدوالّ في methods، والمشتقّات في computed. تبدو الفكرة مرتّبة، لكنها تتفكّك في المكوّنات الكبيرة: منطق ميزة واحدة يتناثر عبر أربعة أقسام مختلفة. Composition API يقلب المعادلة: تنظّم الكود حسب «الاهتمام المنطقي» لا حسب نوعه، فيجتمع كل ما يخصّ ميزة معيّنة في مكان واحد. النتيجة كود أسهل قراءةً، وأهمّ من ذلك: منطق قابل للاستخراج وإعادة الاستخدام.
إن كنت من عالم React، فالتشبيه الأدقّ أن Composition API هو نظير Hooks، لكن دون قيودها المزعجة: لا حساسية لترتيب الاستدعاء، ولا «مصفوفة تبعيّات» (dependency array) تطاردك، ولا مشكلة «القيم القديمة» (stale closures). الدوالّ التفاعلية في Vue تُستدعى مرّة واحدة عند إنشاء المكوّن، لا في كل تحديث.
اللبنة الأولى: ref للقيم البسيطة
كل شيء يبدأ بالتفاعلية (Reactivity). لجعل قيمة «تفاعلية» (أي يتحدّث الواجهة تلقائيًّا حين تتغيّر)، تلفّها بـ ref:
<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
count.value++
}
</script>
<template>
<button @click="increment">العدّاد: {{ count }}</button>
</template>
لاحظ نقطتين تربكان المبتدئين. الأولى: داخل script تصل للقيمة عبر count.value، لكن في القالب (template) تكتب count مباشرةً، لأن Vue «يفكّ» الغلاف تلقائيًّا هناك. الثانية: لا حاجة لدالّة «تعيين حالة» (setState) كما في React؛ تعدّل count.value مباشرةً ويتكفّل Vue بالباقي.
متى ref ومتى reactive؟
هناك طريقتان لإنشاء حالة تفاعلية. ref تصلح لأيّ نوع (أرقام، نصوص، كائنات، مصفوفات)، وتتطلّب .value في الكود. وreactive مخصّصة للكائنات والمصفوفات فقط، وتعيد «وكيلًا» (Proxy) يتتبّع الخصائص المتشعّبة، بلا .value:
import { ref, reactive } from 'vue'
const name = ref('أحمد') // بسيط → ref
const user = reactive({ // كائن → reactive (اختياري)
name: 'أحمد',
age: 30,
})
user.age++ // تعديل مباشر بلا .value
النصيحة العملية التي تريح المبتدئ: استخدم ref افتراضيًّا لكل شيء. إنها متّسقة (دائمًا .value في الكود)، وتتجنّب مزالق reactive مثل فقدان التفاعلية عند تفكيك الكائن (destructuring). كثير من المطوّرين المخضرمين يكتفون بـ ref وحدها.
القيم المشتقّة: computed
حين تحتاج قيمةً تُحسب من قيم أخرى وتتحدّث تلقائيًّا معها، استخدم computed بدل دالّة عادية. الفرق المهمّ: computed «يخزّن» نتيجته (Caching) ولا يعيد الحساب إلا حين تتغيّر تبعيّاته فعلًا:
import { ref, computed } from 'vue'
const price = ref(100)
const quantity = ref(3)
const total = computed(() => price.value * quantity.value)
// total.value = 300، ويتحدّث تلقائيًّا حين يتغيّر السعر أو الكمّية
قاعدة بسيطة: إن كانت القيمة «مشتقّة» من حالة أخرى، فهي computed لا ref. لا تحدّثها يدويًّا، بل دع Vue يحسبها.
المراقبة ودورة الحياة
أحيانًا تريد «تنفيذ شيء» حين تتغيّر قيمة (لا مجرّد اشتقاق قيمة)، كاستدعاء واجهة برمجية. هنا يأتي watch. وأمّا التعامل مع لحظات حياة المكوّن (كجلب بيانات عند ظهوره)، فعبر خطّافات دورة الحياة مثل onMounted:
import { ref, watch, onMounted } from 'vue'
const query = ref('')
const results = ref([])
// راقب تغيّر البحث
watch(query, async (newQuery) => {
results.value = await search(newQuery)
})
// نفّذ مرّة عند ظهور المكوّن
onMounted(() => {
console.log('المكوّن جاهز')
})
إن كنت من React، فـ onMounted يشبه useEffect بمصفوفة تبعيّات فارغة، وwatch يشبه useEffect بتبعيّات محدّدة، لكن بصياغة أوضح وأقلّ عرضةً للخطأ.
الجوهرة الحقيقية: Composables
هنا تتجلّى القوّة الفعلية لـ Composition API. الـ «Composable» دالّة عادية تغلّف منطقًا تفاعليًّا قابلًا لإعادة الاستخدام عبر المكوّنات، تمامًا كـ Hooks في React لكن بلا قيودها. العُرف أن يبدأ اسمها بـ use:
// useCounter.js
import { ref } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const increment = () => count.value++
const decrement = () => count.value--
return { count, increment, decrement }
}
ثم تستخدمه في أيّ مكوّن بسطر واحد:
<script setup>
import { useCounter } from './useCounter'
const { count, increment } = useCounter(10)
</script>
هذا النمط هو ما يجعل الكود قابلًا للتوسّع فعلًا: منطق المصادقة، وجلب البيانات، والتحقّق من النماذج، كلّها تتحوّل إلى composables تكتبها مرّة وتستخدمها في كل مشروعك.
التواصل مع الأب: defineProps وdefineEmits
لاستقبال بيانات من المكوّن الأب، أو إرسال أحداث إليه، توفّر script setup ماكروين جاهزين لا تحتاج لاستيرادهما:
<script setup>
// استقبال خصائص من الأب
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 },
})
// إرسال أحداث للأب
const emit = defineEmits(['save', 'cancel'])
function handleSave() {
emit('save', props.title)
}
</script>
وإن كنت تستخدم TypeScript، يمكن تعريف الأنواع مباشرةً بصياغة أنظف عبر defineProps بوسيط نوعيّ، وهي إحدى أقوى ميزات Vue 3 الحديثة. ومنذ الإصدار 3.5، صار تفكيك الخصائص (destructuring) يحافظ على تفاعليتها تلقائيًّا.
خلاصة عملية للانتقال
إن أردت قاعدةً تبدأ بها فورًا: استخدم script setup دائمًا (أنظف وأسرع)، واعتمد ref افتراضيًّا، واجعل القيم المشتقّة computed، وانقل المنطق المتكرّر إلى composables مبكرًا. لا تحاول حفظ كل واجهة برمجية دفعةً واحدة؛ ابدأ بـ ref وcomputed وonMounted، فهي تغطّي 80% من احتياجاتك اليومية، وأضف البقية حين تحتاجها فعلًا. وأهمّ تحوّل ذهني للقادم من Options API أو غيرها: توقّف عن التفكير «أين أضع هذا الكود؟»، وابدأ بالسؤال «أيّ منطق يخصّ أيّ ميزة؟» — فحين تجتمع كل قطعة مع أخواتها، يصبح الكود قابلًا للقراءة وإعادة الاستخدام دون عناء.
هل وجدت هذا المقال مفيدًا؟