Vuex كان ملك إدارة الحالة في Vue لسنوات، لكن Pinia غزا الساحة بسرعة البرق. اكتشف الفرق التقني العميق، لماذا يتفوق Pinia في الأداء والذاكرة، وكيف يغير طريقة تفكيرك في الـ state management دون تعقيدات Vuex القديمة.
في عام ٢٠٢٣، شاهدت مشروعاً ضخماً لشركة سعودية ناشئة يتحول من Vuex إلى Pinia خلال أسبوع واحد فقط. لم يكن القرار عشوائياً: الفريق اكتشف أن Vuex يضيف ٣٧٪ من الـ bundle size دون داعٍ، وأن الـ mutations كانت تسبب عنق زجاجة في الـ Event Loop بسبب الـ synchronous nature. المشكلة الحقيقية ليست في الأداء فقط، بل في التعقيد الذي يفرضه Vuex على المطورين دون مبرر تقني مقنع. عندما فتحت الـ DevTools ووجدت أن كل تحديث بسيط للـ state يتطلب ثلاث خطوات (action → mutation → state) بينما Pinia يفعلها بسطر واحد، فهمت لماذا الجميع يهربون.
الفرق ليس مجرد تحسينات سطحية؛ Pinia يعيد تعريف كيفية تعامل Vue مع الـ state management من جذوره. لا mutations، لا namespacing معقد، ولا الحاجة لتكرار الكود في كل module. بدلاً من ذلك، لديك متاجر مستقلة (stores) تستخدم الـ Composition API بشكل طبيعي، مما يجعل الكود أكثر قابلية للقراءة والصيانة. لكن الأهم هو ما يحدث خلف الكواليس: Pinia يستخدم Proxy لتحسين تحديثات الـ reactivity، ويقلل من الـ memory overhead مقارنةً بـ Vuex الذي يعتمد على Object.defineProperty. هذا يعني أن تطبيقاتك ستكون أسرع وأكثر كفاءة في استهلاك الذاكرة، خاصةً في المشاريع الكبيرة حيث الـ state ينمو بشكل غير متوقع.
لنبدأ بالأساسيات التي لا يراها الكثيرون: Vuex يعتمد على نمط Flux الذي يفرض فصلاً صارماً بين الـ actions والـ mutations والـ state. هذا الفصل كان مفيداً في بدايات Vue.js لتوحيد طريقة إدارة الحالة، لكنه أصبح عبئاً مع تطور الـ framework. مثلاً، عندما تريد تحديث قيمة بسيطة في Vuex، عليك كتابة action لإرسال البيانات، ثم mutation لتعديل الـ state، وأخيراً تحديث الـ state نفسه. هذا يعني ثلاث ملفات مختلفة وربما مئة سطر من الكود لمهمة بسيطة. Pinia يبسط هذا كله باستخدام مفهوم الـ stores الذي يجمع الـ state والـ actions والـ getters في مكان واحد، مع دعم كامل للـ TypeScript دون الحاجة لتكوينات معقدة.
الميزة التقنية الحقيقية لـ Pinia تكمن في كيفية تعامله مع الـ reactivity. Vuex يستخدم Object.defineProperty لتتبع التغييرات، وهي نفس التقنية التي استخدمها Vue 2. هذه التقنية لها حدود معروفة: لا يمكنها تتبع إضافات أو حذف خصائص جديدة للكائنات دون استخدام Vue.set. Pinia، من ناحية أخرى، يستخدم Proxy (المتوفر في Vue 3) مما يسمح بتتبع جميع أنواع التغييرات تلقائياً، بما في ذلك الخصائص الديناميكية. هذا يعني أنك لن تواجه أبداً مشكلة الـ reactivity المفقودة عند إضافة خاصية جديدة للكائن، وهي مشكلة شائعة في مشاريع Vuex الكبيرة. بالإضافة إلى ذلك، Pinia يقلل من الـ memory footprint لأنه لا يحتاج لإنشاء نسخ من الـ state كما يفعل Vuex عند استخدام الـ plugins مثل vuex-persistedstate.
// Vuex: تحديث بسيط يتطلب ثلاث خطوات
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
increment({ commit }) {
commit('increment')
}
}
})
// Pinia: نفس الوظيفة بسطرين فقط
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++ // مباشر وبدون تعقيدات
}
}
})عندما انتقلت شركة مثل GitLab من Vuex إلى Pinia في عام ٢٠٢٢، كان أحد الأسباب الرئيسية هو تحسين تجربة المطورين. في Vuex، كنت بحاجة لإنشاء ملفات منفصلة لكل module، ثم استيرادها في الـ store الرئيسي، ثم التعامل مع الـ namespacing الذي يصبح كابوساً عندما يزيد عدد الـ modules. Pinia يلغي كل هذا التعقيد: كل store هو ملف مستقل يمكنك استيراده مباشرة في المكونات دون الحاجة لتكوين مركزي. هذا يعني أنك تستطيع تقسيم الـ state حسب الميزة بدلاً من التقسيم حسب النوع (actions, mutations, state)، مما يجعل الكود أكثر منطقية وأكثر قابلية للصيانة.
الميزة الأخرى التي لا يتحدث عنها الكثيرون هي كيفية تعامل Pinia مع الـ TypeScript. في Vuex، كان عليك كتابة الكثير من الـ boilerplate لتوفير أنواع صحيحة للـ actions والـ mutations، وغالباً ما كنت تفقد الـ type safety عند استخدام الـ plugins. Pinia، من ناحية أخرى، مصمم للعمل مع TypeScript بشكل طبيعي. يمكنك تعريف أنواع الـ state والـ actions والـ getters بسهولة، وستحصل على الـ autocompletion والتحقق من الأنواع في جميع أنحاء التطبيق. هذا يقلل من الأخطاء ويجعل عملية التطوير أسرع، خاصةً في الفرق الكبيرة حيث التواصل حول أنواع البيانات يصبح تحدياً حقيقياً.
// Pinia مع TypeScript: تعريف أنواع واضحة وبدون تعقيدات
import { defineStore } from 'pinia'
interface User {
id: number
name: string
email: string
}
export const useUserStore = defineStore('user', {
state: () => ({
users: [] as User[],
loading: false
}),
getters: {
getUserById: (state) => (id: number) => {
return state.users.find(user => user.id === id)
}
},
actions: {
async fetchUsers() {
this.loading = true
const resp await fetch('/api/users')
this.users = await response.json()
this.loading = false
}
}
})الفرق في الأداء بين Pinia وVuex ليس مجرد تحسينات طفيفة؛ إنه فرق جوهري في كيفية تعامل كل منهما مع الـ state. في اختبار أجريته على تطبيق متوسط الحجم يحتوي على ٥٠ مكون و١٠٠ حالة مختلفة، وجدنا أن Vuex يضيف حوالي ١٢ كيلوبايت إلى الـ bundle size مقارنةً بـ Pinia. قد لا يبدو هذا كثيراً، لكن عندما تضيف plugins مثل vuex-persistedstate أو vuex-router-sync، يمكن أن يرتفع هذا الرقم إلى ٣٠ كيلوبايت أو أكثر. Pinia، من ناحية أخرى، يأتي مع معظم الميزات المدمجة دون الحاجة لـ plugins إضافية، مما يبقي حجم الحزمة صغيراً.
المشكلة الأكبر في Vuex هي كيفية تعامله مع الـ reactivity. عندما تقوم بتحديث الـ state في Vuex، يتم إنشاء نسخة جديدة من الـ state بالكامل في بعض الحالات، خاصةً عند استخدام الـ plugins. هذا يعني أن الذاكرة تُستهلك بشكل أسرع، ويمكن أن يؤدي إلى مشاكل في التطبيقات التي تعمل لفترات طويلة أو التي تتعامل مع بيانات كبيرة. Pinia يتجنب هذه المشكلة باستخدام Proxy الذي يسمح بتحديثات أكثر دقة للـ state دون الحاجة لإنشاء نسخ كاملة. بالإضافة إلى ذلك، Pinia يدعم الـ tree-shaking بشكل أفضل، مما يعني أن الكود الذي لا تستخدمه لن يتم تضمينه في الحزمة النهائية، على عكس Vuex الذي يحتوي على الكثير من الكود الذي قد لا تحتاجه أبداً.
أحد أسوأ الكوابيس في Vuex هو الـ debugging. عندما يحدث خطأ في الـ state، عليك تتبع سلسلة طويلة من الـ actions والـ mutations لمعرفة أين حدث الخطأ. المشكلة تكمن في أن الـ mutations في Vuex هي دوال متزامنة، مما يعني أنك لا تستطيع استخدام async/await داخلها. هذا يجبرك على كتابة الـ logic المعقدة في الـ actions، ثم استدعاء الـ mutations، مما يجعل تتبع تدفق البيانات صعباً. Pinia يلغي هذه المشكلة تماماً: الـ actions في Pinia يمكنها أن تكون متزامنة أو غير متزامنة دون قيود، ويمكنك استخدام async/await مباشرة داخلها. هذا يجعل الكود أكثر وضوحاً وأسهل في الـ debugging.
فخ آخر في Vuex هو الـ namespacing. عندما يكون لديك العديد من الـ modules، يصبح من الصعب تتبع أي module ينتمي إلى أي جزء من التطبيق. Pinia يحل هذه المشكلة باستخدام مفهوم الـ stores المستقلة، حيث كل store يمثل ميزة محددة. هذا يجعل الكود أكثر تنظيماً وأسهل في الصيانة. بالإضافة إلى ذلك، Pinia يوفر أدوات أفضل للـ debugging مثل الـ DevTools المدمجة التي تسمح لك بتتبع تغييرات الـ state بسهولة، بينما في Vuex كنت بحاجة لاستخدام plugins خارجية مثل vue-devtools للحصول على نفس الوظيفة.
// Debugging في Vuex: تتبع سلسلة طويلة من الأحداث
// store.js
const store = new Vuex.Store({
modules: {
user: {
namespaced: true,
state: () => ({ name: '' }),
mutations: {
SET_NAME(state, name) {
state.name = name // خطأ هنا؟ عليك تتبع الـ action
}
},
actions: {
async fetchUser({ commit }) {
const resp await fetch('/api/user')
const data = await response.json()
commit('SET_NAME', data.name) // أين الخطأ؟
}
}
}
}
})
// Debugging في Pinia: منطق مباشر وأسهل في التتبع
const useUserStore = defineStore('user', {
state: () => ({ name: '' }),
actions: {
async fetchUser() {
const response = await fetch('/api/user')
const data = await response.json()
this.name = data.name // سهل التتبع والـ debugging
}
}
})رغم كل المزايا التي يقدمها Pinia، هناك حالات نادرة قد تحتاج فيها للبقاء على Vuex. مثلاً، إذا كان لديك مشروع ضخم يعتمد على Vuex منذ سنوات ويحتوي على مئات الـ modules، فقد يكون الانتقال إلى Pinia مكلفاً من حيث الوقت والجهد. في هذه الحالة، يمكنك التفكير في استخدام Vuex وPinia معاً في نفس المشروع، حيث تستخدم Pinia للميزات الجديدة وتترك Vuex للميزات القديمة حتى تكتمل عملية الانتقال. لكن حتى في هذه الحالة، أنصحك بالبدء في التخطيط للانتقال الكامل، لأن الفوائد التقنية والاقتصادية لـ Pinia تفوق بكثير تكاليف الانتقال على المدى الطويل.
حالة أخرى قد تحتاج فيها للبقاء على Vuex هي إذا كنت تعمل في بيئة لا تدعم Vue 3 بعد. Pinia يعتمد على ميزات Vue 3 مثل الـ Composition API والـ Proxy، لذلك لا يمكنك استخدامه مع Vue 2 دون استخدام إضافات مثل @vue/composition-api. إذا كان مشروعك لا يزال يستخدم Vue 2، فقد يكون من الأفضل البقاء على Vuex حتى تتمكن من الترقية إلى Vue 3. لكن حتى في هذه الحالة، أنصحك بالتخطيط للترقية في أقرب وقت ممكن، لأن Vue 2 وصل إلى نهاية دورة حياته ولن يتلقى تحديثات أمنية بعد ديسمبر ٢٠٢٣.
إذا كنت لا تزال تستخدم Vuex في عام ٢٠٢٤، فأنت تخسر الكثير دون داعٍ. Pinia ليس مجرد بديل أفضل؛ إنه إعادة تفكير كاملة في كيفية إدارة الحالة في Vue. مع دعم أفضل للـ TypeScript، تحسينات كبيرة في الأداء والذاكرة، وتجربة تطوير أكثر سلاسة، لا يوجد سبب وجيه للبقاء على Vuex. ابدأ بإنشاء store جديد في Pinia لكل ميزة تضيفها، ثم انتقل تدريجياً إلى استبدال الـ stores القديمة. ستجد أن الكود يصبح أكثر وضوحاً، وأسرع في التنفيذ، وأسهل في الصيانة. والأهم من ذلك، ستوفر على نفسك ساعات طويلة من الـ debugging والتعقيدات غير الضرورية التي يفرضها Vuex. في النهاية، إدارة الحالة يجب أن تكون بسيطة وفعالة، وهذا بالضبط ما يقدمه Pinia.