هل Composition API مجرد صيحة أم ثورة حقيقية في إدارة الحالة والأداء؟ قارننا بين الخيارين تحت المجهر: الذاكرة، المعالج، الـ Event Loop، ومتى ينهار كل منهما في مشاريع الإنتاج الكبيرة.
في أحد أيام الجمعة الحارة، بينما كنت أراجع كود فريق جديد انضم إلينا في الشركة، وقعت عيني على ملف مكون ضخم مكتوب بـ Options API. ٥٠٠ سطر من البيانات، الـ Methods، والـ Computed Properties متداخلة بطريقة تجعل تتبع تدفق البيانات أشبه بمحاولة حل مكعب روبيك معصوب العينين. سألني المطور الجديد: "لماذا ننتقل إلى Composition API؟ Options API يعمل جيداً ولا يوجد خطأ في الكود!" الحقيقة هي أن الكود "يعمل"، لكن خلف الكواليس كان هناك كابوس من الـ Memory Leaks والـ Blocking Calls ينتظر لحظة إطلاق المشروع إلى الإنتاج.
الفرق بين Options API و Composition API ليس مجرد مسألة ذوق أو صيغة كتابة، بل هو صراع بين نموذجين مختلفين تماماً في إدارة الذاكرة والمعالج. في Options API، كل خاصية هي مجرد مفتاح في كائن كبير، بينما في Composition API، كل متغير هو مرجع مستقل في الذاكرة يمكن تتبعه بدقة. هذا الفرق البسيط يصبح كارثياً عندما يتوسع المشروع ويبدأ الـ Event Loop في التعلق بسبب الـ I/O Bound Operations التي لا تنتهي.
لنبدأ بتجربة بسيطة: أنشئ مكوناً بسيطاً يعرض قائمة من ١٠٠٠ عنصر باستخدام Options API. ستجد أن المتصفح يستهلك حوالي ١٢ ميجابايت من الذاكرة. الآن، أعد كتابة نفس المكون باستخدام Composition API مع نفس البيانات بالضبط. المفاجأة؟ استهلاك الذاكرة ينخفض إلى ٨ ميجابايت. لماذا؟ لأن Options API يضطر لإنشاء كائن كبير يحتوي كل الخصائص، الـ Methods، والـ Computed Properties، حتى لو لم تكن مستخدمة في الـ Template. هذا الكائن يبقى في الذاكرة طالما المكون موجود، حتى لو كان الـ Garbage Collector عاجزاً عن الوصول إلى بعض الخصائص بسبب الـ Closures المتداخلة.
في أحد مشاريعنا السابقة، كان لدينا مكون لإدارة الجداول المعقدة يحتوي على أكثر من ٥٠ خاصية computed. بعد ترحيله إلى Composition API، انخفض استهلاك الذاكرة بنسبة ٣٥٪، ولم نعد نرى تلك الرسالة المخيفة في الـ DevTools: "Detached DOM tree". السبب؟ في Composition API، يمكنك تقسيم الكود إلى دوال صغيرة مستقلة، كل منها مسؤول عن جزء محدد من المنطق. هذا يعني أن الـ Garbage Collector يمكنه التخلص من المتغيرات التي لم تعد مستخدمة بسهولة، دون الحاجة للاحتفاظ بكائن المكون بأكمله في الذاكرة.
// Options API - الكائن الكبير الذي يأكل الذاكرة
>
export default {
data() {
return {
items: [],
filters: {},
pagination: {}
}
},
computed: {
filteredItems() {
return this.items.filter(item => this.applyFilters(item))
},
// 49 computed property أخرى هنا...
},
methods: {
applyFilters(item) {
// منطق معقد هنا
},
// 20 method أخرى هنا...
}
}
</script>
// Composition API - تقسيم الذاكرة إلى مراجع مستقلة
<script setup>
import { ref, computed } from 'vue'
const items = ref([])
const filters = ref({})
const pagination = ref({})
// كل computed property هو مرجع مستقل في الذاكرة
const filteredItems = computed(() => {
return items.value.filter(item => applyFilters(item))
})
function applyFilters(item) {
// منطق معقد هنا، لكن الـ Garbage Collector يمكنه التخلص منه بسهولة
}
// دوال أخرى مستقلة...
</script>الـ Event Loop هو قلب أي تطبيق جافاسكريبت، وفي Vue، هو المسؤول عن تحديث الـ DOM وإدارة التفاعلات. المشكلة مع Options API أنه يجبرك على كتابة كل المنطق داخل كائن واحد، مما يعني أن أي عملية ثقيلة (مثل فرز قائمة كبيرة أو معالجة صورة) ستعلق الـ Event Loop بالكامل. في أحد المشاريع التي عملت عليها، كان لدينا مكون يعرض خريطة تفاعلية مع أكثر من ١٠ آلاف نقطة بيانات. باستخدام Options API، كان التفاعل مع الخريطة بطيئاً للغاية، وكان الـ FPS ينخفض إلى ١٠ فقط عند التكبير والتصغير. بعد ترحيل الكود إلى Composition API، أصبح بإمكاننا تقسيم المنطق إلى دوال صغيرة يمكن تشغيلها في Web Workers، مما رفع الـ FPS إلى ٦٠ دون أي تجميد.
الفرق الرئيسي هنا هو أن Composition API يسمح لك بكتابة كود أكثر "وظيفياً" (functional)، حيث يمكنك فصل العمليات الثقيلة إلى دوال نقية لا تعتمد على حالة المكون. هذا يعني أنه يمكنك تشغيل هذه الدوال في Web Workers أو حتى على السيرفر دون الحاجة لإعادة كتابة الكود بالكامل. في المقابل، Options API يجبرك على كتابة كل شيء داخل كائن المكون، مما يجعل من الصعب جداً فصل العمليات الثقيلة عن الـ Event Loop الرئيسي.
// Options API - كل شيء داخل كائن واحد، الـ Event Loop يتجمد
methods: {
processLargeDataset() {
// عملية ثقيلة هنا تعلق الـ Event Loop
const result = this.items.sort((a, b) => {
// منطق فرز معقد
})
return result
}
}
// Composition API - فصل المنطق الثقيل إلى دالة نقية
function processLargeDataset(items) {
// نفس منطق الفرز، لكن يمكن تشغيله في Web Worker
return items.sort((a, b) => {
// منطق فرز معقد
})
}
// داخل المكون
const processedItems = computed(() => {
return processLargeDataset(items.value)
})إذا كنت تعمل في مشروع يستخدم TypeScript، فستعرف أن Options API هو كابوس من حيث الـ Type Safety. السبب؟ لأن كل شيء في Options API هو مجرد مفتاح في كائن، مما يجعل من الصعب على TypeScript استنتاج الأنواع بدقة. مثلاً، إذا كان لديك computed property يعتمد على خاصية في data، فسيضطر TypeScript إلى افتراض أن هذه الخاصية قد تكون من أي نوع، مما يفقدك كل فوائد الـ Type Checking. في أحد المشاريع التي عملت عليها، كان لدينا مكون يحتوي على أكثر من ٢٠ computed property، وكلها تعتمد على بعضها البعض. باستخدام Options API، كان TypeScript عاجزاً عن اكتشاف الأخطاء في الأنواع، مما أدى إلى عدد كبير من الـ Runtime Errors التي كان من الممكن تجنبها بسهولة باستخدام Composition API.
في Composition API، كل متغير هو مرجع مستقل يمكن تحديد نوعه بدقة. هذا يعني أن TypeScript يمكنه تتبع الأنواع عبر كل الدوال والمتغيرات، مما يقلل من فرص حدوث أخطاء في وقت التشغيل. بالإضافة إلى ذلك، يمكنك استخدام الـ Generics مع Composition API لإنشاء مكونات أكثر مرونة وقابلة لإعادة الاستخدام دون فقدان الـ Type Safety. مثلاً، يمكنك إنشاء مكون جدول عام يقبل أي نوع من البيانات، مع الحفاظ على التحقق من الأنواع في كل خطوة.
// Options API مع TypeScript - كابوس الأنواع
lang="ts">
export default {
data() {
return {
items: [] as any[], // لا يمكن تحديد النوع بدقة
filters: {} as any
}
},
computed: {
filteredItems(): any[] {
// TypeScript لا يمكنه تتبع الأنواع هنا
return this.items.filter(item => this.applyFilters(item))
}
},
methods: {
applyFilters(item: any): boolean {
// منطق الفلترة، لكن الأنواع غير معروفة
return true
}
}
}
</script>
// Composition API مع TypeScript - أنواع دقيقة وقابلة للتتبع
<script setup lang="ts">
interface Item {
id: number
name: string
// خصائص أخرى...
}
const items = ref<Item[]>([])
const filters = ref<Record<string, string>>({})
const filteredItems = computed((): Item[] => {
return items.value.filter(item => applyFilters(item))
})
function applyFilters(item: Item): boolean {
// TypeScript يعرف نوع item هنا
return true
}
</script>إحدى أكبر مزايا Composition API هي القدرة على إعادة استخدام المنطق عبر المكونات بسهولة. يمكنك إنشاء دوال صغيرة مستقلة (تُسمى Composable Functions) يمكن استيرادها واستخدامها في أي مكون. هذا يبدو رائعاً في النظرية، لكن في الممارسة، يمكن أن يؤدي إلى مشكلة جديدة: الـ Dependency Hell. في أحد المشاريع، أنشأ فريق التطوير أكثر من ٥٠ Composable Function، بعضها يعتمد على البعض الآخر بطريقة معقدة. النتيجة؟ عند محاولة تعديل أحد الـ Composables، كان علينا تتبع تأثير هذا التعديل عبر عشرات المكونات، مما جعل الصيانة كابوساً حقيقياً.
في المقابل، Options API يجبرك على كتابة كل المنطق داخل المكون نفسه، مما يجعل من السهل تتبع تدفق البيانات والتبعيات. نعم، هذا قد يؤدي إلى تكرار الكود أحياناً، لكنه يمنع حدوث الـ Dependency Hell الذي قد يحدث مع Composition API. الحل الوسط؟ استخدم Composition API لإعادة استخدام المنطق المشترك فقط، ولا تحاول تقسيم كل شيء إلى Composable Functions صغيرة. مثلاً، إذا كان لديك منطق فلترة معقد تستخدمه في مكونين فقط، فلا داعي لإنشاء Composable Function له، بل يمكنك ببساطة استيراد الدالة واستخدامها مباشرة في المكونين.
// Composition API - إعادة الاستخدام عبر Composable Functions
// useFilters.js
import { ref, computed } from 'vue'
export function useFilters(items) {
const filters = ref({})
const filteredItems = computed(() => {
return items.value.filter(item => applyFilters(item, filters.value))
})
return { filters, filteredItems }
}
// المكون
setup>
import { ref } from 'vue'
import { useFilters } from './useFilters'
const items = ref([])
const { filters, filteredItems } = useFilters(items)
</script>
// Options API - إعادة الاستخدام عبر Mixins (مع مشاكلها المعروفة)
<script>
import filtersMixin from './filtersMixin'
export default {
mixins: [filtersMixin],
data() {
return {
items: []
}
}
}
</script>في بيئة التطوير، قد لا تلاحظ الفرق الكبير بين Options API و Composition API، لكن في الإنتاج، خاصة مع التطبيقات الكبيرة والمعقدة، يصبح الفرق واضحاً جداً. في أحد المشاريع التي عملت عليها، كان لدينا تطبيق يحتوي على أكثر من ٢٠٠ مكون، يستخدم Options API. عند تحميل الصفحة الرئيسية، كان المتصفح يستغرق أكثر من ٣ ثوانٍ لتحميل كل المكونات وإنشاء الـ Virtual DOM. بعد ترحيل المشروع إلى Composition API، انخفض وقت التحميل إلى أقل من ثانية واحدة. السبب؟ Composition API يولد كوداً أصغر وأكثر كفاءة، حيث لا يضطر Vue لإنشاء كائنات كبيرة لكل مكون، بل يمكنه التعامل مع المتغيرات والدوال بشكل مباشر.
لكن هذا لا يعني أن Composition API هو الحل السحري لكل المشاكل. في بعض الحالات، خاصة مع المكونات الصغيرة والبسيطة، قد يكون Options API أسرع في التنفيذ. السبب؟ لأن Vue مُحسّن للتعامل مع كائنات Options API بشكل مباشر، بينما يحتاج Composition API إلى بعض المعالجة الإضافية لترجمة الكود إلى شيء يمكن لـ Vue فهمه. مثلاً، إذا كان لديك مكون بسيط يعرض قائمة من العناصر دون أي منطق معقد، فقد يكون Options API أسرع قليلاً في التنفيذ. لكن بمجرد أن يبدأ المكون في النمو ويصبح أكثر تعقيداً، فإن Composition API يتفوق بوضوح.
بعد كل هذه المقارنة، قد تتساءل: أيهما تختار؟ الحقيقة هي أن الاختيار يعتمد على طبيعة المشروع وحجمه. إذا كنت تعمل على مشروع صغير أو متوسط الحجم، ولا تتوقع أن ينمو كثيراً، فقد يكون Options API خياراً جيداً. إنه سهل الفهم والتعلم، ويوفر هيكلية واضحة للمكونات الصغيرة. لكن إذا كنت تعمل على مشروع كبير ومعقد، أو تتوقع أن ينمو كثيراً في المستقبل، فإن Composition API هو الخيار الأفضل بلا منازع. إنه يوفر مرونة أكبر في إدارة الحالة، وأداء أفضل في الذاكرة والمعالج، ودعم أفضل لـ TypeScript.
في رأيي الشخصي، Composition API هو مستقبل Vue، وليس مجرد بديل. لقد رأينا كيف أن معظم المكتبات والإضافات الجديدة لـ Vue 3 تعتمد على Composition API بشكل أساسي. حتى فريق Vue نفسه يشجع على استخدامه في المشاريع الجديدة. لكن هذا لا يعني أن Options API سيختفي قريباً. في الواقع، يمكنك استخدام كلا النموذجين في نفس المشروع، حيث يسمح لك Vue 3 بخلطهما معاً. مثلاً، يمكنك كتابة المكونات الجديدة باستخدام Composition API، بينما تحتفظ بالمكونات القديمة المكتوبة بـ Options API دون تغيير.
إذا كنت تبدأ مشروعاً جديداً اليوم، فلا تفكر مرتين: استخدم Composition API منذ اليوم الأول. ابدأ بكتابة كل المكونات الجديدة باستخدام setup>، وقم بإنشاء Composable Functions لإعادة استخدام المنطق المشترك. وإذا كان لديك مشروع قديم يستخدم Options API، فلا داعي للذعر. ابدأ بترحيل المكونات الكبيرة والمعقدة أولاً، واترك المكونات الصغيرة والبسيطة كما هي. بهذه الطريقة، ستستفيد من مزايا Composition API دون الحاجة لإعادة كتابة المشروع بالكامل من الصفر. تذكر: الهدف ليس مجرد كتابة كود يعمل، بل كتابة كود يمكن صيانته وتوسيعه بسهولة في المستقبل دون أن يصبح كابوساً على فريق التطوير.