تخطَّ إلى المحتوى

⚛️ شرح React

خطّاف useReducer

الدرس 14 من 32· ⏱ 3 دقائق قراءة· 🗓 آخر تحديث: ١٧ يوليو ٢٠٢٦

لماذا useReducer؟

حين تصبح الحالة معقّدة (عدة قيم مترابطة، أو تحديثات متعدّدة المنطق)، يصبح useState فوضويًا. useReducer ينظّم منطق التحديث في مكان واحد.

الفكرة: reducer + dispatch

  • reducer: دالة تأخذ الحالة الحالية و"إجراءً (action)" وتُرجع الحالة الجديدة.
  • dispatch: نطلق بها الإجراءات.
import { useReducer } from "react";

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() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "reset" })}>تصفير</button>
    </>
  );
}

مثال أقوى: قائمة مهام

function todosReducer(todos, action) {
  switch (action.type) {
    case "add":
      return [...todos, { id: Date.now(), text: action.text, done: false }];
    case "toggle":
      return todos.map((t) =>
        t.id === action.id ? { ...t, done: !t.done } : t
      );
    case "delete":
      return todos.filter((t) => t.id !== action.id);
    default:
      return todos;
  }
}

function TodoApp() {
  const [todos, dispatch] = useReducer(todosReducer, []);

  return (
    <button onClick={() => dispatch({ type: "add", text: "مهمة" })}>
      أضف
    </button>
  );
}

كل منطق التحديث في مكان واحد منظّم.

useReducer مقابل useState

useStateuseReducer
الأنسب لـحالة بسيطةحالة معقّدة/مترابطة
المنطقمتفرّقمركّز في reducer

🔑 ابدأ بـ useState. حين يكثر منطق التحديث أو تترابط القيم، انتقل إلى useReducer. ويتكامل ممتازًا مع useContext لإدارة حالة عامّة.

نمط متقدم: Reducer مع Domain Exceptions

عند التعامل مع أخطاء API داخل reducer، استخدم ApiError و NetworkError من react-base-repository بدلاً من Error العادي:

import { useReducer } from "react";
import { ApiError, NetworkError, UserDTO, HttpClient, HttpUserRepository } from "./lib/react-base-repository";

interface UserState {
  users: UserDTO[];
  loading: boolean;
  error: string | null;
}

type UserAction =
  | { type: "FETCH_START" }
  | { type: "FETCH_SUCCESS"; payload: UserDTO[] }
  | { type: "FETCH_ERROR"; payload: string };

function userReducer(state: UserState, action: UserAction): UserState {
  switch (action.type) {
    case "FETCH_START":
      return { ...state, loading: true, error: null };
    case "FETCH_SUCCESS":
      return { users: action.payload, loading: false, error: null };
    case "FETCH_ERROR":
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
}

// استخدام reducer مع Repository
const http = new HttpClient("/api");
const userRepo = new HttpUserRepository(http);

function UserList() {
  const [state, dispatch] = useReducer(userReducer, { users: [], loading: false, error: null });

  async function loadUsers() {
    dispatch({ type: "FETCH_START" });
    try {
      const users = await userRepo.getUsers();
      dispatch({ type: "FETCH_SUCCESS", payload: users });
    } catch (err) {
      if (err instanceof ApiError) {
        dispatch({ type: "FETCH_ERROR", payload: err.message });
      } else if (err instanceof NetworkError) {
        dispatch({ type: "FETCH_ERROR", payload: "تعذّر الاتصال بالخادم" });
      } else {
        dispatch({ type: "FETCH_ERROR", payload: "حدث خطأ غير متوقع" });
      }
    }
  }

  if (state.error) return <p role="alert">{state.error}</p>;
  return (
    <div>
      <button onClick={loadUsers} disabled={state.loading}>تحميل</button>
      {state.loading ? <p>جارٍ التحميل...</p> : (
        <ul>{state.users.map(u => <li key={u.id}>{u.name}</li>)}</ul>
      )}
    </div>
  );
}

الأخطاء الشائعة

  • ❌ تعديل الحالة مباشرةً داخل reducer → أرجِع كائنًا/مصفوفة جديدة دائمًا.
  • ❌ نسيان default في الـ reducer → أرجِع الحالة كما هي.
  • ❌ استخدام useReducer لحالة بسيطةuseState أبسط.

خلاصة

useReducer ينظّم الحالة المعقّدة: عرّف reducer يعالج الإجراءات، واطلقها بـ dispatch({ type }). أرجِع حالة جديدة دائمًا (لا تعدّل الأصل). مثالي للحالة المترابطة، خاصةً مع Context.

شرح خطّاف useReducer — React بالعربي
خطّاف useReducerReact بالعربي · The Code Fix

📚 لمزيد من التعمّق في React، راجِع التوثيق الرسمي لـ React.

هل كان هذا الدرس مفيدًا؟