🚀 احتراف إدارة الحالة باستخدام useReducer في React

لقد تعلمت سابقاً كيف تستخدم useState لإدارة الحالة (State)، وهي أداة رائعة وبسيطة. ولكن، ماذا لو أصبحت الحالة "معقدة"؟ ماذا لو كان لديك عدة قيم مرتبطة ببعضها، أو كانت الحالة تتغير بناءً على عمليات منطقية متعددة؟ 🤯

هنا يأتي دور البطل الجديد: useReducer.


🧐 ما هو useReducer وما لماذا نحتاجه؟

useReducer هو "هوك" (Hook) يُستخدم لإدارة الحالة في React، وهو بديل متطور لـ useState. بدلاً من تحديث الحالة مباشرة، يقوم useReducer باتباع نمط محدد يسمى "Reducer Pattern".

ببساطة: تخيل أن useReducer مثل "موظف الاستقبال" في شركة. أنت لا تدخل إلى المكتب وتغير الملفات بنفسك، بل تعطي الموظف "طلب" (Action)، وهو يقوم بتنفيذ التغيير المناسب بناءً على نوع الطلب.

متى نستخدمه بدلاً من useState؟

  1. عندما تكون الحالة عبارة عن كائن (Object) يحتوي على عدة خصائص.
  2. عندما تعتمد الحالة القادمة على الحالة السابقة بشكل معقد.
  3. عندما يكون لديك منطق تحديث يتكرر في أكثر من مكان.

🛠️ المكونات الأساسية لـ useReducer

لكي يعمل useReducer بشكل صحيح، نحتاج إلى ثلاثة عناصر أساسية:

1. الحالة الابتدائية (Initial State) 🚩

وهي القيمة التي يبدأ بها التطبيق (مثلاً: رقم صفر، أو كائن فارغ).

2. دالة الـ Reducer (The Reducer Function) ⚙️

هي دالة تأخذ شيئين: الحالة الحالية (State) و الطلب (Action)، ثم تعيد حالة جديدة. (state, action) => newState

3. دالة الإرسال (Dispatch Function) 📨

هي الدالة التي نستخدمها لإرسال "الطلب" إلى الـ Reducer لإخباره بأن شيئاً ما قد حدث.


👨‍💻 مثال عملي: عداد بسيط (Counter)

لنطبق هذه المفاهيم على مثال "العداد" الشهير، ولكن باستخدام useReducer.

import React, { useReducer } from 'react';

// 1. Define the initial state
// تعريف الحالة الابتدائية
const initialState = { count: 0 };

// 2. Define the reducer function
// تعريف دالة الـ reducer التي تحدد كيف تتغير الحالة
function reducer(state, action) {
  switch (action.type) {
    case 'increment': // إذا كان الطلب هو زيادة
      return { count: state.count + 1 };
    case 'decrement': // إذا كان الطلب هو نقصان
      return { count: state.count - 1 };
    case 'reset':     // إذا كان الطلب هو تصفير
      return { count: 0 };
    default:
      return state; // في حال لم يتم التعرف على الطلب، ابقِ الحالة كما هي
  }
}

function Counter() {
  // 3. Initialize useReducer
  // تهيئة الـ hook: يعطينا الحالة الحالية ودالة الإرسال
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div style={{ textAlign: 'center', marginTop: '20px' }}>
      <h1>Count: {state.count}</h1>
      
      {/* We use dispatch to send an action object */}
      {/* نستخدم dispatch لإرسال كائن يحتوي على نوع العملية */}
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </div>
  );
}

export default Counter;

🔍 شرح تفصيلي للكود السابق

  1. initialState: بدأنا بكائن { count: 0 } لأننا نريد تتبع الرقم.
  2. reducer(state, action): هذه الدالة هي "العقل المدبر". استخدمنا فيها switch للتأكد من نوع العملية (action.type). إذا كان النوع increment فإنها تعيد نسخة جديدة من الحالة مضافاً إليها 1.
  3. dispatch({ type: '...' }): لاحظ أننا لا نقول setState(count + 1)، بل نقول لـ React: "يا React، أرسلي طلباً للـ Reducer بأن العملية هي increment"، والـ Reducer هو من يقرر كيف يتم التحديث.

💡 ملاحظات ذهبية للمبتدئين

  • عدم التعديل المباشر (Immutability): لاحظ أننا في الـ Reducer لم نكتب state.count = state.count + 1 بل أعدنا كائناً جديداً بالكامل { count: state.count + 1 }. في React، يجب دائماً إرجاع حالة جديدة وليس تعديل القديمة.
  • تسمية الـ Actions: يفضل دائماً أن يكون الـ Action عبارة عن كائن يحتوي على خاصية type (مثل { type: 'ADD_USER' }) لأن هذا يجعل الكود منظماً وسهل القراءة.

🎯 ملخص سريع

  • useState للحالات البسيطة (نص، رقم، بولين).
  • useReducer للحالات المعقدة (كائنات، مصفوفات، عمليات متعددة).