Skip to content

فلسفة إدارة الحالة

🎯 السؤال الأساسي

عندما يكبر التطبيق، كيف يمكن للمكونات مشاركة البيانات ومزامنتها بشكل أنيق؟ قد تواجه هذا المأزق: يضيف المستخدم منتجًا إلى سلة التسوق في صفحة المنتج، لكن عداد السلة في الرأس لا يتحدّث؛ مكونان غير مرتبطين يحتاجان نفس البيانات، لكنك لا تعرف كيف تمررها. سيأخذك هذا الفصل من "تمرير البيانات الفوضوي" إلى "إدارة الحالة الواضحة".


1. لماذا "المكونات وإدارة الحالة"؟

1.1 من الورشة الصغيرة إلى المصنع: تطور تطوير الواجهات الأمامية

قبل أن نبدأ رسميًا، دعني أسألك سؤالًا: هل جرّبت يومًا تحضير وجبة كبيرة في المطبخ؟

إذا كنت تطهو وعاءً من المعكرونة لنفسك فقط، فالأمر بسيط — قدرٌ واحد، قليل من المعكرونة، بعض التوابل، وتنتهي في عشر ثوانٍ. لكن إذا كنت ستفتح مطعمًا يخدم مئات الزبائن يوميًا، فلا يمكنك بعد الآن أن "تفعل ما يحلو لك". ستحتاج إلى وصفات موحدة، وتقسيم واضح للعمل، وعمليات شراء موحدة — بهذه الطريقة فقط يمكنك ضمان جودة ثابتة لكل طبق وكفاءة عالية في التحضير.

تطوير الواجهات الأمامية لا يختلف. عندما تكتب مشروعًا صغيرًا بمفردك، يمكنك وضع الكود في أي مكان. لكن عندما يكبر الفريق ويصبح المشروع أكثر تعقيدًا، ستحتاج إلى طريقة منهجية لتنظيم الكود وإدارة البيانات. هذا هو بالضبط ما تحله المكونات وإدارة الحالة.

🤔 ما هو "المكون" وما هي "الحالة"؟

قبل أن نستمر، دعنا نشرح مصطلحين أساسيين:

المكون (Component): مثل قطع الليغو، كل قطعة هي جزء مستقل له شكله ولونه ووظيفته الخاصة. يمكنك تجميع عدة قطع معًا لبناء قلعة معقدة. في تطوير الواجهات الأمامية، الزر، النموذج، شريط التنقل — كل منها يمكن أن يكون مكونًا.

الحالة (State): هي "ذاكرة" المكون. مثلًا، زر "يتذكر" ما إذا كان في حالة "معطل" أو "مفعل"؛ مكون سلة التسوق "يتذكر" المنتجات الموجودة بداخله. الحالة تتغير، وتغير الحالة يؤدي إلى تحديث الواجهة.

المكونات + إدارة الحالة = كود منظم + تدفق بيانات واضح

🏠 نمط الورشة الصغيرة

  • الكود مكتوب في ملف واحد، مثل طهي كل شيء في قدر واحد
  • البيانات تتنقل في كل مكان، مثل نادل يركض حاملًا الأطباق في أرجاء المطعم
  • تعديل في مكان قد يؤثر على أماكن أخرى، مثل وضع الكثير من الملح فيفسد الطبق كله

🏭 نمط المصنع

  • الكود مقسم إلى مكونات، مثل مطعم مقسم إلى صالة ومطبخ وقسم مشتريات
  • البيانات تُدار مركزيًا، مثل وجود مستودع موحد ونظام توزيع
  • تأثير التعديلات واضح النطاق، مثل تغيير طبق لا يؤثر على المطعم كله

1.2 قصة واقعية من الأخطاء: لماذا تحتاج إلى فهم إدارة الحالة

قد تقول: "ألا أستخدم Vue/React؟ أليس لديهما بالفعل إدارة حالة؟" دعني أحكِ لك قصة واقعية، وستفهم لماذا الفهم المنهجي للمكونات وإدارة الحالة مهم جدًا.

قصة أخطاء شياو مي

شياو مي كانت مديرة منتج في شركة تجارة إلكترونية وانتقلت إلى تطوير الواجهات الأمامية، وتولت للتو إعادة بناء ميزة سلة التسوق في الشركة. كانت تستخدم سابقًا مشاريع قديمة بحقبة jQuery، والآن تريد التحول إلى Vue 3.

فكرت شياو مي: "منطق سلة التسوق بسيط، فقط أخزن مصفوفة." فبدأت بكتابة الكود:

  • في مكون صفحة تفاصيل المنتج، استخدمت مصفوفة cart لتخزين بيانات السلة
  • في مكون صفحة سلة التسوق، عرّفت مصفوفة أخرى cartItems
  • في مكون شريط التنقل العلوي، كان هناك متغير cartCount

ظهرت المشاكل بسرعة:

  1. البيانات غير متزامنة: أضاف المستخدم منتجًا في صفحة التفاصيل، لكن بيانات صفحة السلة لم تتحدّث
  2. كود مكرر: اضطرت شياو مي لكتابة عدة دوال "إضافة إلى السلة"، موزعة على مكونات مختلفة
  3. صعوبة الصيانة: طلب قسم العمليات إضافة ميزة "إفراغ السلة"، فاكتشفت شياو مي أنها بحاجة لتعديل ثلاثة أماكن

لاحقًا، استشارت المهندس المعماري للواجهات الأمامية أتشيانغ، الذي نظر إلى الكود وقال فورًا: "لقد ارتكبتِ خطأً كبيرًا في إدارة الحالة — نفس البيانات مخزنة في عدة أماكن."

الحل بسيط جدًا: استخدم Pinia لإنشاء إدارة حالة عامة لسلة التسوق، بحيث تقرأ جميع المكونات وتكتب البيانات من نفس المكان. بعد هذا التعديل، حُلّت جميع المشاكل.

منذ ذلك الحين، فهمت شياو مي درسًا: بدون فهم المكونات وإدارة الحالة، ستكتب "كود معكرونة" يصعب صيانته.

💡 الدرس الأساسي

المكونات وإدارة الحالة ليست "ميزة إضافية" في الأطر، بل هي حجر الزاوية في تطوير الواجهات الأمامية الحديث. فهمها يمكنك من تصميم معمارية واضحة، وكتابة كود قابل للصيانة، والعمل بسلاسة ضمن فريق.


2. المفاهيم الأساسية: فهم جوهر المكونات

🤔 ما هو "التفكير بالمكونات"؟

التفكير بالمكونات هو طريقة لتفكيك الواجهات المعقدة إلى وحدات كود مستقلة، قابلة لإعادة الاستخدام، وذات مسؤولية واحدة.

للتشبيه: تخيل أنك تجمع حاسوبًا. ستشتري المعالج والذاكرة والقرص الصلب وبطاقة الرسوميات بشكل منفصل، ثم تجمعها معًا. كل قطعة لها وظيفة محددة، ويمكنك استبدال أي قطعة في أي وقت دون التأثير على الأجزاء الأخرى.

المكونات تجعل كود الواجهة الأمامية "معياريًا" بنفس الطريقة — كل مكون مسؤول عن شؤونه الخاصة، ويتعاون مع المكونات الأخرى عبر واجهات واضحة.

2.1 فهم المكونات من خلال تشبيه المطعم

لنستخدم تشبيه المطعم لفهم الأفكار الأساسية للمكونات:

المفهوم🍽️ تشبيه المطعمالدور الفعليمثال ملموس
المكونأقسام المطعم المختلفة (الصالة، المطبخ، المشتريات)كل قسم مسؤول عن شؤونهمكون الزر مسؤول عن النقر، مكون النموذج مسؤول عن الإدخال
Props (الخصائص)قائمة الطلبات التي يعطيها الزبون للنادلالمكون الأب يمرر البيانات للمكون الابنالمكون الأب يمرر "اسم المستخدم" لمكون الصورة الرمزية
Events (الأحداث)النادل يخطر المطبخ "يوجد طلب جديد"المكون الابن يخطر المكون الأب بما حدثمكون الزر يخبر المكون الأب "تم النقر عليّ"
State (الحالة)"قائمة الطلبات الحالية" في المطبخالبيانات المخزنة داخل المكونمكون السلة يتذكر المنتجات الموجودة بداخله

📊 ماذا يمكنك أن ترى من الجدول؟

لنفسر هذا الجدول سطرًا بسطر:

المكون: مثلما يوجد في المطعم أقسام مختلفة، تتكون صفحة الواجهة الأمامية من مكونات مختلفة. كل مكون هو جزء مستقل له مسؤوليته الخاصة.

Props: هذه هي طريقة المكون الأب في "تمرير البيانات" إلى المكون الابن. كما يخبر الزبون النادل بما يريد أن يأكل عندما يطلب، يمكن للمكون الأب أيضًا تمرير البيانات (مثل اسم المستخدم، معلومات المنتج) إلى المكون الابن عبر props. ملاحظة: props هي "أحادية الاتجاه"، يمكن فقط التمرير من الأب إلى الابن، وليس العكس.

Events: عندما يحتاج المكون الابن إلى إخطار المكون الأب (مثل النقر على زر، إرسال نموذج)، فإنه يُطلق حدثًا. كما يخطر النادل المطبخ "ابدأ الطهي" بعد استلام الطلب. هذا يحافظ على أحادية اتجاه تدفق البيانات — لا يمكن للمكون الابن تعديل بيانات المكون الأب مباشرة، بل يمكنه فقط "إرسال رسالة".

State: هذه هي "ذاكرة" المكون الداخلية. كما يحتاج المطبخ إلى تذكر الطلبات الحالية، يحتاج المكون أيضًا إلى تذكر حالته (مثل المنتجات في السلة، ما إذا كان الزر معطلًا). عندما تتغير الحالة، يُحدّث المكون الواجهة تلقائيًا.

🌳Component HierarchyComponent relationships like a family tree
Imagine working in a company org chart: the CEO (root component) sits at the top, departments (parent components) report below, and employees (child components) live inside each department. That is a component tree.
👑
App (Root)
CEO - global management
📌
Header
Navigation department
📄
Main Content
Main content department
📑
Sidebar
Sidebar team
🛍️
ProductList
Product list team
🏷️
ProductCard
Product card employee
🔻
Footer
Footer department
👆 Click any node above to inspect its responsibility.
💡Core idea:Components are like an org chart: parents coordinate the whole, children handle focused responsibilities. Data flows downward, events report upward.

2.2 Props و Events: "القناة الرسمية" للتواصل بين المكونات الأب والابن

في أطر الواجهات الأمامية (Vue, React)، Props و Events هما الطريقة القياسية للتواصل بين المكونات الأب والابن.

مثال Vue:

vue
<!-- Parent.vue - المكون الأب -->
<template>
  <div>
    <!-- مثل إعطاء النادل قائمة الطلبات، مرر البيانات عبر props -->
    <Child
      :user-name="currentUser.name"
      :is-admin="currentUser.isAdmin"
      @delete-user="handleDelete"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const currentUser = ref({
  name: '张三',
  isAdmin: true
})

const handleDelete = (userId) => {
  console.log('حذف المستخدم:', userId)
  // معالجة منطق الحذف
}
</script>
vue
<!-- Child.vue - المكون الابن -->
<template>
  <div class="user-card">
    <h3>{{ userName }}</h3>
    <span v-if="isAdmin" class="badge">مدير</span>
    <button @click="requestDelete">حذف المستخدم</button>
  </div>
</template>

<script setup>
// استقبال البيانات الممررة من المكون الأب
const props = defineProps({
  userName: { type: String, required: true },
  isAdmin: { type: Boolean, default: false }
})

// تعريف الأحداث التي يمكن إطلاقها
const emit = defineEmits(['delete-user'])

const requestDelete = () => {
  // إخطار المكون الأب عبر الحدث
  emit('delete-user', props.userName)
}
</script>

💡 المبدأ الأساسي

Props للأسفل، Events للأعلى — هذا هو القانون الذهبي للتواصل بين المكونات.

  • المكون الأب يمرر البيانات إلى المكون الابن عبر props (مثل توزيع المهام على المرؤوسين)
  • المكون الابن يخطر المكون الأب بما حدث عبر events (مثل تقرير المرؤوس عن العمل)

هذا يحافظ على وضوح تدفق البيانات وأحاديته، ويتجنب فوضى "يمكن لأي أحد تعديل البيانات".

📦Props Data FlowOne-way delivery from parent to child
Imagine working at a delivery company: packages (data) can only move from sender (parent component) to recipient (child component). The recipient cannot change the package directly, and can only call back with an event.
👨 Parent Component (Sender)
Package content:Alex (25 years old)
Package color:Light
📮 Send package:
:user:theme
One-way props flow
👦 Child Component (Recipient)
📬 Receive package:
userAlex (25 years old)
themeLight
💡Core idea:Props are one-way data flow. The parent is the sender and the child is the recipient. Children should not mutate props directly; they emit events so the parent can update state.

2.3 تدفق البيانات أحادي الاتجاه: لماذا لا يمكن تعديل props مباشرة؟

يقع العديد من المبتدئين في خطأ شائع: تعديل قيمة props مباشرة داخل المكون الابن.

vue
<!-- ❌ الطريقة الخاطئة -->
<script setup>
const props = defineProps({
  count: { type: Number, default: 0 }
})

// تعديل props مباشرة - هذا ممنوع!
props.count = 10  // سيؤدي إلى خطأ
</script>

لماذا لا يمكن تعديل props مباشرة؟

تخيل أنك استعرت كتابًا من المكتبة (props)، ثم كتبت عليه وخربشته (تعديل props). الأشخاص الآخرون الذين يستعيرون هذا الكتاب (المكونات الأخرى) سيرون خربشاتك أيضًا، مما يسبب الفوضى. الطريقة الصحيحة هي: إذا احتجت إلى تعديل البيانات، دع المكون الأب هو من يفعل ذلك، والمكون الابن فقط "يطلب التعديل".

vue
<!-- ✅ الطريقة الصحيحة -->
<script setup>
const props = defineProps({
  count: { type: Number, default: 0 }
})

const emit = defineEmits(['update-count'])

// طلب التعديل من المكون الأب عبر الحدث
const increment = () => {
  emit('update-count', props.count + 1)
}
</script>

3. من "الفوضى" إلى "النظام": رحلة تطور تواصل المكونات

🤔 لماذا نحتاج إلى التطور؟

مع كبر المشروع، يصبح التواصل بين المكونات أكثر تعقيدًا. لنرى كيف تطور فريق حقيقي خطوة بخطوة نحو حل واضح لإدارة الحالة.

هذا ليس مجرد "ترقية للأدوات"، بل تغير كامل في طريقة التفكير — من "تمرير البيانات بشكل عشوائي" إلى "تصميم تدفق بيانات واضح".

3.1 الصورة الشاملة للتطور

الجدول التالي يعرض المراحل الأربع لتطور طرق تواصل المكونات، ويمكنك رؤية كيف تم حل المشاكل خطوة بخطوة:

المرحلةطريقة التواصلالمشاكل النموذجيةالتغير الأساسي
المرحلة الأولى: التمرير الحرالتعديل المباشر، المتغيرات العامةعدم تزامن البيانات، صعوبة التصحيحلا توجد قواعد، أي طريقة تمرير مسموحة
المرحلة الثانية: Props/Eventsالتواصل القياسي بين الأب والابنProps Drilling (التمرير الطبقي)توجد قواعد، لكن التداخل العميق مزعج
المرحلة الثالثة: مكتبات إدارة الحالةVuex/Redux/Piniaتكلفة التعلم، الكود النمطيإدارة مركزية للبيانات، تصحيح سهل
المرحلة الرابعة: الحلول الحديثةالدوال التركيبية/الذريةتحتاج فهم مفاهيم جديدةأكثر مرونة، أكثر إيجازًا
📡Event BusMessage delivery like a radio station
Imagine working at a radio station: any department (component) can publish messages through the station (Event Bus), and every radio (listener) can receive them without knowing the sender.
📻
Radio Station (Event Bus)
📌
Header
📻 Listening
📑
Sidebar
📻 Listening
🛍️
ProductList
📻 Listening
🛒
Cart
📻 Listening
👆 Click any department above to simulate broadcasting; other active departments will receive it.
💡Core idea:Event Bus is like a radio station: any component can send or receive messages without knowing the other side exists. It suits simple cross-component communication, but listeners should be removed when components unmount.

📊 ماذا يمكنك أن ترى من الجدول؟

لنفسر هذا الجدول سطرًا بسطر:

المرحلة الأولى ← المرحلة الثانية: من "لا قواعد" إلى "توجد قواعد". هذه نقلة نوعية — تبدأ في استخدام تواصل props/events القياسي، ويصبح تدفق البيانات واضحًا. لكن الثمن هو أنه عندما يكون تسلسل المكونات عميقًا، يجب تمرير البيانات طبقة بطبقة، وهذا مزعج جدًا (وهو ما يسمى Props Drilling).

المرحلة الثانية ← المرحلة الثالثة: من "الإدارة الموزعة" إلى "الإدارة المركزية". تبدأ في استخدام مكتبات إدارة الحالة مثل Vuex/Redux، وتضع البيانات المشتركة في "مخزن" عام، بحيث تقرأ وتكتب جميع المكونات البيانات من هنا. هذا يحل مشكلة Props Drilling، لكن تكلفة التعلم تصبح أعلى.

المرحلة الثالثة ← المرحلة الرابعة: من "الثقيل" إلى "الخفيف". الحلول الجديدة (مثل Composition API في Vue 3، و Hooks في React) تجعل إدارة الحالة أكثر مرونة وإيجازًا. لم تعد مضطرًا لاستخدام مخزن عام، بل يمكنك تجميع وحدات حالة صغيرة حسب الحاجة.

الخلاصة: التطور ليس مجرد "استبدال أدوات أفضل"، بل ترقية كاملة لطريقة التفكير — من تمرير البيانات بشكل عشوائي، إلى تصميم تدفق بيانات واضح.

3.2 المرحلة الأولى: التمرير الحر — بداية الفوضى

لماذا نسميها "التمرير الحر"؟ لأنه في هذه المرحلة لا توجد أي قواعد، البيانات تُمرر بأي طريقة — متغيرات عامة، تعديل مباشر، نواقل أحداث في كل مكان.

سيناريو نموذجي: بيانات سلة التسوق موزعة في كل مكان

javascript
// مكون صفحة تفاصيل المنتج
export default {
  data() {
    return {
      localCart: []  // يحتفظ بنسخته الخاصة من بيانات السلة
    }
  },
  methods: {
    addToCart(product) {
      this.localCart.push(product)
      // محاولة المزامنة مع المكونات الأخرى
      window.cart = this.localCart  // ❌ متغير عام!
    }
  }
}

// مكون صفحة سلة التسوق
export default {
  data() {
    return {
      cartItems: []  // نسخة أخرى من بيانات السلة
    }
  },
  mounted() {
    // محاولة القراءة من المتغير العام
    this.cartItems = window.cart || []  // ❌ غير موثوق!
  }
}

// مكون شريط التنقل العلوي
export default {
  data() {
    return {
      cartCount: 0  // نسخة ثالثة من البيانات!
    }
  },
  mounted() {
    // استطلاع دوري للتغييرات (كم هذا سخيف)
    setInterval(() => {
      this.cartCount = window.cart?.length || 0
    }, 1000)  // ❌ أداء سيء!
  }
}

خصائص هذه المرحلة:

  • الإيجابيات: بسيطة ومباشرة، لا توجد تكلفة تعلم
  • السلبيات: بيانات موزعة، صعوبة في المزامنة، صعوبة في التصحيح، فوضى عارمة

3.3 المرحلة الثانية: Props/Events — بناء القواعد

فوضى التمرير الحر جعلت الفريق يدرك: نحن بحاجة إلى قواعد. فبدأوا في استخدام طرق التواصل القياسية التي توفرها الأطر: props و events.

سيناريو نموذجي: Props Drilling (حفر الخصائص)

vue
<!-- المكون الجد: App.vue -->
<template>
  <div class="app">
    <!-- تمرير معلومات المستخدم طبقة بطبقة -->
    <Layout :user-name="userName" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Layout from './Layout.vue'

const userName = ref('张三')
</script>
vue
<!-- الطبقة الوسطى: Layout.vue -->
<template>
  <div class="layout">
    <Header :user-name="userName" />  <!-- مجرد تمرير، لا يستخدم -->
    <Main>
      <Page :user-name="userName" />  <!-- مجرد تمرير، لا يستخدم -->
    </Main>
  </div>
</template>

<script setup>
const props = defineProps({
  userName: String
})
</script>
vue
<!-- المكان الذي يحتاجه فعليًا: Header.vue -->
<template>
  <header>
    <span>{{ userName }}</span>  <!-- أخيرًا تم استخدامه -->
  </header>
</template>

<script setup>
const props = defineProps({
  userName: String
})
</script>

خصائص هذه المرحلة:

  • الإيجابيات: تدفق بيانات واضح، أحادي الاتجاه، سهل الفهم
  • السلبيات: Props Drilling (التمرير الطبقي مزعج جدًا)، صعوبة في التواصل بين المكونات غير المرتبطة

🤔 ما هو Props Drilling؟

Props Drilling يعني: البيانات يجب أن تمر عبر العديد من المكونات الوسيطة، طبقة بطبقة، لكن هذه المكونات الوسيطة لا تستخدم هذه البيانات فعليًا.

كما لو كنت تريد توصيل طرد إلى شخص في الطابق الخامس، لكن القواعد تنص على أن كل طابق يجب أن يوقع على الاستلام. سكان الطوابق الأول والثاني والثالث والرابع فقط "يمررون الطرد"، هم لا يحتاجونه، لكن يجب أن يشاركوا. من الواضح أن هذا مزعج جدًا.

3.4 المرحلة الثالثة: مكتبات إدارة الحالة — الإدارة المركزية

أدت معاناة Props Drilling إلى ظهور مكتبات إدارة الحالة (Vuex، Redux، Pinia). فكرتها الأساسية: ضع البيانات المشتركة في "مستودع" عام، بحيث تقرأ وتكتب جميع المكونات البيانات من هنا.

سيناريو نموذجي: إدارة سلة التسوق باستخدام Pinia

javascript
// stores/cart.js - حالة سلة التسوق العامة
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  // جميع بيانات سلة التسوق مركزة هنا
  const items = ref([])

  // خاصية محسوبة: عدد المنتجات
  const itemCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )

  // دالة: إضافة منتج
  const addItem = (product) => {
    const existing = items.value.find(item => item.id === product.id)
    if (existing) {
      existing.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }

  return {
    items,
    itemCount,
    addItem
  }
})
vue
<!-- مكون صفحة تفاصيل المنتج -->
<script setup>
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()

const addToCart = (product) => {
  cart.addItem(product)  // استدعاء مباشر، لا حاجة للتمرير الطبقي
}
</script>
vue
<!-- مكون شريط التنقل العلوي -->
<template>
  <header>
    <span>سلة التسوق ({{ cart.itemCount }})</span>
  </header>
</template>

<script setup>
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()  // قراءة مباشرة، مزامنة تلقائية
</script>

خصائص هذه المرحلة:

  • الإيجابيات: إدارة مركزية للبيانات، حل Props Drilling، أدوات تصحيح قوية
  • السلبيات: تكلفة التعلم، الحاجة لكتابة كود إضافي (كود نمطي)، قد تكون مبالغة في التصميم للمشاريع البسيطة

3.5 المرحلة الرابعة: الحلول الحديثة — المرونة والإيجاز

مكتبات إدارة الحالة قوية، لكن لديها مشكلة "استخدام المدفع لقتل بعوضة". بالنسبة للمشاريع الصغيرة والمتوسطة، ظهرت حلول أكثر مرونة وخفة.

سيناريو نموذجي: إعادة استخدام منطق الحالة باستخدام Composable/Hooks

javascript
// composables/useCart.js - منطق سلة تسوق قابل لإعادة الاستخدام
import { ref, computed } from 'vue'

export function useCart() {
  const items = ref([])

  const itemCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )

  const addItem = (product) => {
    const existing = items.value.find(item => item.id === product.id)
    if (existing) {
      existing.quantity++
    } else {
      items.value.push({ ...product, quantity: 1 })
    }
  }

  return {
    items,
    itemCount,
    addItem
  }
}
vue
<!-- الاستخدام في أي مكون -->
<script setup>
import { useCart } from '@/composables/useCart'

// كل استدعاء ينشئ نسخة حالة جديدة
// مناسب للحالة المحلية داخل المكون
const { items, itemCount, addItem } = useCart()
</script>

خصائص هذه المرحلة:

  • الإيجابيات: مرنة، خفيفة، قابلة للتجميع، تستخدم حسب الحاجة
  • السلبيات: تحتاج فهم التفكير التركيبي، المشاركة بين المكونات تحتاج معالجة إضافية

4. تفصيل مكتبات إدارة الحالة: Vuex مقابل Pinia مقابل Redux

🤔 كيف تختار مكتبة إدارة الحالة؟

عند مواجهة مكتبات إدارة الحالة المختلفة، قد تحتار: أي واحدة تختار؟

في الواقع، لا توجد مكتبة "أفضل"، بل هناك "الأنسب". عند الاختيار، ضع في اعتبارك هذه العوامل:

  • أي إطار تستخدم؟ Vue استخدم Pinia، React استخدم Redux/Zustand
  • ما حجم المشروع؟ المشاريع الصغيرة استخدم Composable، المشاريع الكبيرة استخدم مكتبة إدارة الحالة
  • خبرة الفريق؟ اختر ما يعرفه الفريق، أو ما تكلفة تعلمه منخفضة

المحتوى التالي سيشرح بالتفصيل خصائص وسيناريوهات استخدام مكتبات إدارة الحالة الرئيسية.

4.1 مقارنة مكتبات إدارة الحالة الرئيسية

الخاصيةReduxVuexPiniaZustand
الإطار المناسبReactVueVueReact
منحنى التعلمحادمتوسطلطيفلطيف
الكود النمطيكثيرمتوسطقليلقليل جدًا
TypeScriptجيدجيدممتازممتاز
أدوات التصحيحقويةجيدةممتازةجيدة
سيناريو الاستخداممشاريع كبيرةمشاريع Vue 2/3 متوسطة وكبيرةمشاريع Vue 3 جديدةمشاريع React صغيرة ومتوسطة

📊 ماذا يمكنك أن ترى من الجدول؟

لنفسر هذا الجدول سطرًا بسطر:

Redux: مكتبة إدارة الحالة الكلاسيكية في بيئة React. إيجابياتها: قواعد صارمة، أدوات تصحيح قوية. سلبياتها: كود نمطي كثير، منحنى تعلم حاد. مناسبة للمشاريع الكبيرة والفرق التي تحتاج قواعد صارمة.

Vuex: مكتبة إدارة الحالة الرسمية في حقبة Vue 2. فلسفة التصميم مشابهة لـ Redux، لكنها أكثر تناغمًا مع نظام التفاعلية في Vue. لا يزال يمكن استخدامها الآن، لكن للمشاريع الجديدة يُنصح بـ Pinia.

Pinia: مكتبة إدارة الحالة من الجيل الجديد الموصى بها رسميًا لـ Vue 3. قواعد بسيطة، دعم ممتاز لـ TypeScript، تكلفة تعلم منخفضة. هذا هو الخيار الأول لمشاريع Vue 3.

Zustand: مكتبة إدارة حالة خفيفة في بيئة React. API بسيطة للغاية، تقريبًا لا يوجد كود نمطي. مناسبة لمشاريع React الصغيرة والمتوسطة.

📊State Management ComparisonWhere different tools fit
Imagine shopping in a supermarket: small trips use a basket (Zustand), larger trips use a cart (Pinia), and enterprise purchasing uses professional logistics (Redux). Pick the tool that matches the need.
Tool
Difficulty
Size
Framework
🔄Redux
Hard
7KB
React/Vue/Angular
🌿Vuex
Medium
4KB
Vue Only
🍍Pinia
Easy
2KB
Vue 3 Only
🐻Zustand
Easy
1KB
React Only
🍍
Pinia

Intuitive, type-safe, flexible Vue Store

🎯 Best for
first choice for new Vue 3 projects, TypeScript-heavy apps, simpler state management
✅ Pros
lightweight design; native TypeScript support
❌ Cons
Vue 3 focused; younger ecosystem
💡Recommendation:For new Vue 3 projects, choose Pinia. For small and medium React projects, choose Zustand. For large enterprise apps, choose Redux Toolkit. Match the tool to project scale.

4.2 Pinia عمليًا: الخيار الموصى به لـ Vue 3

Pinia هي مكتبة إدارة الحالة الموصى بها رسميًا من فريق Vue، مصممة خصيصًا لـ Vue 3. إنها أبسط وأسهل استخدامًا من Vuex.

لماذا سميت Pinia؟

Pinia هي كلمة إسبانية تعني "الأناناس". الأناناس فاكهة مكونة من العديد من الزهيرات الصغيرة، كل زهيرة مستقلة، لكنها ككل تشكل وحدة موحدة. هذا يشبه تمامًا فلسفة تصميم Pinia — كل store مستقل، لكن يمكن استخدامها معًا بشكل تركيبي.

المفاهيم الأساسية:

عرض مثال الكود الكامل
javascript
// stores/user.js - إدارة حالة المستخدم
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // 1. State: تخزين البيانات
  const userInfo = ref(null)
  const isLoggedIn = computed(() => !!userInfo.value)

  // 2. Actions: دوال تعديل البيانات
  const login = async (username, password) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ username, password })
    })
    const user = await response.json()
    userInfo.value = user  // تعديل مباشر، Pinia يتولى التفاعلية
  }

  const logout = () => {
    userInfo.value = null
  }

  // 3. Getters: خصائص محسوبة
  const displayName = computed(() => {
    return userInfo.value?.name || 'زائر'
  })

  return {
    userInfo,
    isLoggedIn,
    login,
    logout,
    displayName
  }
})

الاستخدام في المكونات:

vue
<template>
  <div class="user-panel">
    <span v-if="user.isLoggedIn">مرحبًا، {{ user.displayName }}</span>
    <button v-if="user.isLoggedIn" @click="user.logout">تسجيل الخروج</button>
    <button v-else @click="showLoginDialog">تسجيل الدخول</button>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'

// الحصول على store مباشرة، كل المحتوى تفاعلي
const user = useUserStore()

const showLoginDialog = () => {
  // إظهار حوار تسجيل الدخول...
}
</script>

مزايا Pinia:

الميزةالشرحمقارنة مع Vuex
API بسيطةلا حاجة لـ mutations، تعديل مباشر لـ stateVuex يحتاج فصل mutations و actions
صديقة لـ TypeScriptاستدلال نوعي أصلي، لا حاجة لإعدادات إضافيةVuex يحتاج تعريفات أنواع معقدة
تقسيم تلقائي للوحداتكل ملف store يصبح وحدة تلقائيًاVuex يحتاج إعداد namespaced يدويًا
حجم أصغرحوالي 1KB بعد الحزمVuex حوالي 3KB
🍍Vuex vs PiniaOld and new Vue state management
Imagine ordering at a restaurant: Vuex is like a traditional restaurant where different departments (state/mutations/actions) fill different forms; Pinia is like a fast-food counter where one composable API handles the flow.
🌿VuexClassic
✅ Options API
✅ Separate State / Mutations / Actions
❌ More boilerplate
❌ Weaker TypeScript support
🍍PiniaRecommended
✅ Composition API
✅ Removes Mutations and simplifies code
✅ Excellent TypeScript support
✅ Automatic code splitting
Pinia Code Example
// stores/counter.js
export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  function increment() {
    count.value++
  }

  return { count, increment }
})
💡Recommendation:Use Pinia directly for new Vue 3 projects. Its syntax is simpler and TypeScript support is stronger. Vuex is still fine for existing apps, but gradual migration to Pinia is recommended.

4.3 Redux عمليًا: الخيار الكلاسيكي لـ React

Redux هي مكتبة إدارة الحالة الأكثر كلاسيكية في بيئة React، وتتميز بتدفق البيانات الأحادي الصارم.

لماذا سمي Redux؟

Redux هو اختصار لـ "Reduced Flux". Flux هو نمط معمارية تطبيق اقترحته Facebook مبكرًا، وRedux بسط مفاهيم Flux، لذا سمي "Reduced Flux".

المبادئ الأساسية:

  1. مصدر بيانات واحد: حالة التطبيق بالكامل مخزنة في شجرة كائن واحدة
  2. الحالة للقراءة فقط: الطريقة الوحيدة لتغيير الحالة هي إطلاق action
  3. استخدام دوال نقية للتعديل: Reducer يجب أن يكون دالة نقية
عرض مثال الكود الكامل
javascript
// 1. تعريف Action Types
const ADD_TODO = 'ADD_TODO'
const TOGGLE_TODO = 'TOGGLE_TODO'

// 2. تعريف Action Creators
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { id: Date.now(), text, completed: false }
})

const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  payload: { id }
})

// 3. تعريف Reducer (دالة نقية)
const initialState = {
  todos: []
}

const todoReducer = (state = initialState, action) => {
  switch (action.type) {
    case ADD_TODO:
      return {
        ...state,
        todos: [...state.todos, action.payload]
      }
    case TOGGLE_TODO:
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      }
    default:
      return state
  }
}

// 4. إنشاء Store
import { createStore } from 'redux'
const store = createStore(todoReducer)

الاستخدام في React:

jsx
import { useSelector, useDispatch } from 'react-redux'

function TodoList() {
  // قراءة الحالة
  const todos = useSelector(state => state.todos)

  // الحصول على دالة dispatch
  const dispatch = useDispatch()

  return (
    <ul>
      {todos.map(todo => (
        <li
          key={todo.id}
          onClick={() => dispatch(toggleTodo(todo.id))}
          style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
        >
          {todo.text}
        </li>
      ))}
    </ul>
  )
}

إيجابيات وسلبيات Redux:

الإيجابياتالسلبيات
تدفق بيانات صارم، سهل التصحيحكود نمطي كثير، منحنى تعلم حاد
تصحيح السفر عبر الزمن (Time Travel)الحالات البسيطة تحتاج أيضًا كتابة كود كثير
بيئة غنية من البرمجيات الوسيطةغير مناسب للمشاريع الصغيرة
تحديثات حالة قابلة للتنبؤيحتاج فهم مفاهيم البرمجة الوظيفية
🔄Redux Data FlowA one-way circular data pipeline
Imagine working in a library: readers (View) submit borrowing forms (Action), librarians (Reducer) review them and update inventory records (Store), then the notice board (View update) refreshes.
Current inventory:0books
💡Core idea:Redux is a one-way data-flow loop: View dispatches Action → Reducer pure function handles it → Store updates → View re-renders. State is predictable and easier to debug.
MobX ReactivityAutomatic dependency tracking
Imagine watching a magic show: when the magician (Observable) changes an object, every audience member watching it (Reaction) notices automatically without manual notifications.
📦Observable State
Learn MobX
Understand reactivity
🔄Automatic Reaction
Total:2 items
Completed:1 items
💡Core idea:MobX automatically tracks relationships between state and reactions. When state changes, related updates run automatically. You change data, and the UI follows.
🐻Zustand & JotaiLightweight React state management
Imagine working in a convenience store: Zustand is like one shared warehouse, while Jotai splits products into small independent cells (Atoms) that can be used on demand.
📦Single StoreManage all state centrally
Minimal APINo Provider wrapper required
🎯Fine-grained SubscriptionOnly rerender the components that need it
// Zustand Store
import { create } from 'zustand'

const useStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({
    bears: state.bears + 1
  }))
}))

// Use inside a component
function BearCounter() {
  const bears = useStore((state) => state.bears)
  return <div>{bears} bears around here</div>
}
💡Recommendation:Zustand fits small and medium projects with a clean API. Jotai fits scenarios that need fine-grained control and modular state. Both support TypeScript and do not require a Provider.

5. دليل عملي: كيف تصمم إدارة الحالة؟

🤔 متى تحتاج مكتبة إدارة الحالة؟

ليس كل مشروع يحتاج مكتبة إدارة حالة. قبل إدخالها، اسأل نفسك بعض الأسئلة:

  1. كم عدد المكونات التي تحتاج مشاركة هذه البيانات؟

    • إذا كان فقط 2-3 مكونات، استخدم props/events يكفي
    • إذا كان 5+ مكونات، فكر في مكتبة إدارة الحالة
  2. هل هذه البيانات تتغير كثيرًا؟

    • إذا كانت شبه ثابتة (مثل معلومات المستخدم)، استخدم Provide/Inject
    • إذا كانت تتغير كثيرًا (مثل سلة التسوق)، استخدم مكتبة إدارة الحالة
  3. ما حجم الفريق؟

    • فردي أو فريق صغير: حل بسيط يكفي
    • فريق كبير: تحتاج قواعد صارمة وأدوات تصحيح قوية

تذكر: ابدأ بسيطًا، ورقِّ حسب الحاجة.

5.1 مبادئ تصميم الحالة

بغض النظر عن حل إدارة الحالة الذي تختاره، يجب أن تتبع هذه المبادئ:

المبدأ الأول: مصدر بيانات واحد

نفس البيانات يجب أن تخزن في مكان واحد فقط. لا تعرّف نفس البيانات بشكل مكرر في مكونات متعددة.

javascript
// ❌ خطأ: البيانات موزعة في كل مكان
const ProductDetail = { cart: [] }
const CartPage = { items: [] }
const Header = { count: 0 }

// ✅ صحيح: البيانات مدارة مركزيًا
const cartStore = { items: [] }  // مصدر البيانات الوحيد

المبدأ الثاني: عدم القابلية للتغيير

عند تعديل الحالة، يجب إنشاء كائن جديد، بدلًا من تعديل الكائن الأصلي مباشرة.

javascript
// ❌ خطأ: تعديل مباشر
state.items.push(newItem)

// ✅ صحيح: إنشاء كائن جديد
state.items = [...state.items, newItem]

المبدأ الثالث: الحالة للأعلى، الأحداث للأسفل

الحالة المشتركة يجب أن توضع في أقرب مكون سلف مشترك أو في store عام، وليس موزعة في المكونات الأبناء.

vue
<!-- ❌ خطأ: الحالة في المكون الابن -->
<Parent>
  <Child :data="childData" @update="childData = $event" />
</Parent>

<!-- ✅ صحيح: الحالة في المكون الأب -->
<Parent>
  <Child :data="parentData" @update="parentData = $event" />
</Parent>

5.2 حالة عملية: تصميم حالة سلة تسوق لمتجر إلكتروني

لنطبق المعرفة السابقة بشكل شامل، ونصمم حل إدارة حالة لسلة تسوق متجر إلكتروني.

تحليل المتطلبات:

  • صفحة قائمة المنتجات يمكنها إضافة منتجات إلى السلة
  • صفحة سلة التسوق يمكنها عرض وتعديل الكمية وحذف المنتجات
  • شريط التنقل العلوي يعرض عدد منتجات السلة
  • دعم تحديد/إلغاء تحديد المنتجات، وحساب السعر الإجمالي للمنتجات المحددة
  • استمرار البيانات في localStorage

تصميم الحالة (Pinia):

javascript
// stores/cart.js
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCartStore = defineStore('cart', () => {
  // ============ State (الحالة) ============
  const items = ref([])  // قائمة منتجات السلة
  const selectedIds = ref([])  // معرفات المنتجات المحددة

  // استعادة البيانات من localStorage
  const initFromStorage = () => {
    const stored = localStorage.getItem('cart')
    if (stored) {
      try {
        const data = JSON.parse(stored)
        items.value = data.items || []
        selectedIds.value = data.selectedIds || []
      } catch (e) {
        console.error('فشل قراءة بيانات السلة:', e)
      }
    }
  }

  // الاستمرار في localStorage
  const persist = () => {
    localStorage.setItem('cart', JSON.stringify({
      items: items.value,
      selectedIds: selectedIds.value
    }))
  }

  // ============ Getters (خصائص محسوبة) ============
  const itemCount = computed(() =>
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )

  const totalPrice = computed(() =>
    items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )

  const selectedItems = computed(() =>
    items.value.filter(item => selectedIds.value.includes(item.id))
  )

  const selectedTotalPrice = computed(() =>
    selectedItems.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
  )

  // ============ Actions (الدوال) ============
  const addItem = (product) => {
    const existing = items.value.find(item => item.id === product.id)
    if (existing) {
      existing.quantity += product.quantity || 1
    } else {
      items.value.push({
        ...product,
        quantity: product.quantity || 1
      })
    }
    persist()
  }

  const updateQuantity = (productId, quantity) => {
    const item = items.value.find(item => item.id === productId)
    if (item) {
      if (quantity <= 0) {
        removeItem(productId)
      } else {
        item.quantity = quantity
        persist()
      }
    }
  }

  const removeItem = (productId) => {
    items.value = items.value.filter(item => item.id !== productId)
    selectedIds.value = selectedIds.value.filter(id => id !== productId)
    persist()
  }

  const toggleSelection = (productId) => {
    const index = selectedIds.value.indexOf(productId)
    if (index > -1) {
      selectedIds.value.splice(index, 1)
    } else {
      selectedIds.value.push(productId)
    }
    persist()
  }

  // التهيئة
  initFromStorage()

  return {
    // State
    items,
    selectedIds,
    // Getters
    itemCount,
    totalPrice,
    selectedItems,
    selectedTotalPrice,
    // Actions
    addItem,
    updateQuantity,
    removeItem,
    toggleSelection
  }
})

الاستخدام في المكونات:

vue
<!-- صفحة تفاصيل المنتج: ProductDetail.vue -->
<template>
  <div class="product-detail">
    <h2>{{ product.name }}</h2>
    <p class="price">¥{{ product.price }}</p>
    <button @click="addToCart">أضف إلى السلة</button>
  </div>
</template>

<script setup>
import { useCartStore } from '@/stores/cart'

const props = defineProps({
  product: Object
})

const cart = useCartStore()

const addToCart = () => {
  cart.addItem({
    id: props.product.id,
    name: props.product.name,
    price: props.product.price
  })
}
</script>
vue
<!-- شريط التنقل العلوي: Header.vue -->
<template>
  <header class="header">
    <div class="logo">متجري</div>
    <nav>
      <RouterLink to="/">الرئيسية</RouterLink>
      <RouterLink to="/cart">
        سلة التسوق ({{ cart.itemCount }})
      </RouterLink>
    </nav>
  </header>
</template>

<script setup>
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()  // استخدام مباشر، استجابة تلقائية للتغييرات
</script>

6. الأخطاء الشائعة ودليل تجنبها

⚠️ هذه الأخطاء، 90% من المبتدئين يقعون فيها

في ممارسة إدارة الحالة، بعض الأخطاء شائعة جدًا. دعني ألخص أكثر الأخطاء شيوعًا، وكيفية تجنبها.

6.1 الخطأ الأول: تعديل Props أو State مباشرة

الكود الخاطئ:

javascript
// ❌ تعديل props مباشرة
props.user.name = 'لي سي'

// ❌ تعديل state في Vuex مباشرة
store.state.user.name = 'لي سي'

// ❌ تعديل عنصر مصفوفة مباشرة
state.items[0].name = 'اسم جديد'

لماذا هذا غير صحيح؟

أطر الواجهات الأمامية (Vue/React) تحتاج إلى "تتبع" تغييرات البيانات لتتمكن من تحديث الواجهة تلقائيًا. إذا عدلت الكائن أو المصفوفة مباشرة، قد لا يتمكن الإطار من اكتشاف التغيير، مما يؤدي إلى عدم تحديث الواجهة.

الطريقة الصحيحة:

javascript
// ✅ Vue 3 / Pinia: تعديل مباشر للخصائص العلوية
store.user.name = 'لي سي'  // Pinia يتولى التفاعلية تلقائيًا

// ✅ Vue 2 / Vuex: عبر mutation
mutations: {
  UPDATE_USER_NAME(state, newName) {
    state.user.name = newName
  }
}

// ✅ تعديل المصفوفة: إنشاء مصفوفة جديدة
state.items = state.items.map((item, index) =>
  index === 0 ? { ...item, name: 'اسم جديد' } : item
)

6.2 الخطأ الثاني: تعديل الحالة داخل Getter

الكود الخاطئ:

javascript
// ❌ تعديل الحالة داخل getter
getters: {
  doubleCount(state) {
    state.count *= 2  // تأثير جانبي!
    return state.count
  }
}

لماذا هذا غير صحيح؟

Getter يجب أن يكون "دالة نقية"، مسؤولة فقط عن الحساب وإرجاع القيمة، ولا يجب أن يكون له أي آثار جانبية (تعديل الحالة). إذا عدلت الحالة داخل getter، فقد يؤدي ذلك إلى حلقات لا نهائية ومشاكل يصعب تصحيحها.

الطريقة الصحيحة:

javascript
// ✅ Getter يحسب فقط، لا يعدل
getters: {
  doubleCount(state) {
    return state.count * 2
  }
}

// ✅ إذا احتجت التعديل، استخدم action
actions: {
  doubleCountAndSave({ commit }) {
    commit('SET_DOUBLE_COUNT')
  }
}

6.3 الخطأ الثالث: نسيان تنظيف مستمعي الأحداث

الكود الخاطئ:

javascript
// ❌ نسيان إلغاء الاشتراك
export default {
  created() {
    EventBus.$on('cart-updated', this.handleCartUpdate)
  }
  // المكون تدمر، لكن المستمع ما زال موجودًا!
}

لماذا هذا غير صحيح؟

إذا تدمر المكون لكن مستمع الحدث ما زال موجودًا، فسيؤدي ذلك إلى تسرب في الذاكرة (الذاكرة المحجوزة لا يمكن تحريرها). في تطبيقات الصفحة الواحدة، يتنقل المستخدم باستمرار بين الصفحات، وهذه المستمعات غير النظيفة تتراكم أكثر فأكثر، مما يؤدي في النهاية إلى بطء الصفحة.

الطريقة الصحيحة:

javascript
// ✅ إلغاء الاشتراك في الوقت المناسب
export default {
  created() {
    EventBus.$on('cart-updated', this.handleCartUpdate)
  },
  beforeUnmount() {  // Vue 3 يستخدم beforeUnmount، Vue 2 يستخدم beforeDestroy
    EventBus.$off('cart-updated', this.handleCartUpdate)
  }
}

6.4 الخطأ الرابع: الإفراط في استخدام إدارة الحالة

الكود الخاطئ:

javascript
// ❌ وضع كل الحالات في store
const store = useStore()
store.inputValue = 'إدخال المستخدم'
store.isModalOpen = true
store.currentTab = 'profile'

لماذا هذا غير صحيح؟

ليس كل الحالات تحتاج أن توضع في store العام. إذا كانت حالة تستخدم فقط في مكون واحد (مثل قيمة حقل الإدخال، حالة فتح/إغلاق النافذة المنبثقة)، فضعها داخل المكون. الإفراط في استخدام إدارة الحالة يجعل الكود معقدًا.

الطريقة الصحيحة:

javascript
// ✅ الحالة المحلية تدار داخل المكون
const inputValue = ref('')

// ✅ فقط الحالات التي تحتاج مشاركة توضع في store
const userInfo = useUserStore()  // مكونات متعددة تحتاج معلومات المستخدم
const cart = useCartStore()  // مكونات متعددة تحتاج بيانات السلة

7. الخلاصة والتوصيات

7.1 مراجعة النقاط الأساسية

لنستخدم جدولًا لمراجعة المفاهيم الأساسية للمكونات وإدارة الحالة:

المفهومشرح في جملة واحدةالمشكلة التي يحلهاالأدوات النموذجية
المكوناتتقسيم الواجهة إلى أجزاء مستقلة قابلة لإعادة الاستخدامإعادة استخدام الكود، فصل المسؤولياتمكونات Vue/React
Propsالمكون الأب يمرر البيانات للمكون الابنالتواصل بين الأب والابنمدمج في Vue/React
Eventsالمكون الابن يخطر المكون الأب بما حدثالتواصل بين الابن والأبمدمج في Vue/React
Stateالبيانات المخزنة داخل المكونتذكر حالة المكونمدمج في Vue/React
مكتبة إدارة الحالةإدارة مركزية للحالة العامة المشتركةالتواصل بين المكونات، Props DrillingPinia، Redux، Zustand
مصدر البيانات الوحيدنفس البيانات تخزن في مكان واحد فقطعدم اتساق البيانات، صعوبة المزامنةالمبدأ الأساسي لمكتبات إدارة الحالة

7.2 توصيات الاختيار حسب السيناريو

السيناريوالحل الموصى بهالسبب
تواصل المكونات الأب والابنProps + Eventsمدمج في الإطار، بسيط ومباشر
تمرير القيم عبر المستوياتProvide / Injectتجنب التمرير الطبقي
الحالة المحلية داخل المكونref / useStateبسيط، لا حاجة لأدوات إضافية
مشروع Vue متوسطPiniaموصى به رسميًا، تكلفة تعلم منخفضة
مشروع React متوسطZustandبسيط جدًا، لا كود نمطي
مشروع Vue كبيرPinia + قواعدمرن وقابل للتوسع
مشروع React كبيرRedux Toolkitقواعد صارمة، بيئة غنية
إعادة استخدام المنطق بين المكوناتComposable / Hooksمرن، قابل للتجميع

7.3 توصيات التعلم

للمبتدئين:

  1. أتقن الأساسيات أولًا: افهم المفاهيم الأساسية مثل props و events و state
  2. ابدأ من مشاريع صغيرة: لا تبدأ مباشرة بمكتبات إدارة الحالة
  3. اكتب كودًا أكثر: مهما تعلمت من النظرية، لا شيء يضاهي الممارسة العملية

للمتقدمين:

  1. اقرأ الكود المصدري: افهم كيفية عمل Pinia/Redux
  2. تعلم الأنماط: افهم أنماط التصميم الشائعة (مثل نمط المراقب، نمط النشر والاشتراك)
  3. تابع البيئة: تعلم الأدوات ذات الصلة (مثل DevTools، البرمجيات الوسيطة)

تذكر هذه المبادئ الأساسية:

  1. ابدأ بسيطًا: لا تدخل مكتبات إدارة حالة معقدة قبل الأوان
  2. مصدر بيانات واحد: تجنب تخزين نفس البيانات في أماكن متعددة
  3. عدم القابلية للتغيير: أنشئ كائنات جديدة عند تعديل الحالة، بدلًا من التعديل المباشر
  4. اختر حسب الحاجة: اختر الحل المناسب بناءً على حجم المشروع وظروف الفريق

آمل أن يساعدك هذا المقال في بناء فهم شامل للمكونات وإدارة الحالة. عندما تواجه مشاكل تدفق بيانات معقدة في مشاريعك الفعلية، ستعرف من أين تبدأ، وكيف تصمم، وكيف تنفذ.