Kodokon kodokon.com

useReducer للحالة المعقدة

نظّم الحالة المعقدة بمُخفّض نقي يجمع الانتقالات في مكان واحد ويستبعد التركيبات المستحيلة.

9 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

بمجرد أن يُنسّق مكوّن عدة قيم حالة تتطور معًا - حالة طلب، وبعض البيانات، وخطأ - فإن تكثير استدعاءات useState يبعثر منطق الانتقال عبر كل معالج حدث. تعرف العَرَض: ثلاثة setters يجب استدعاؤها بالترتيب الصحيح، وعاجلاً أم آجلاً تظهر حالة غير متسقة، مثل حالة success بجانب خطأ غير فارغ. يقلب useReducer المسؤولية: تصبح الحالة كائنًا واحدًا، ويمرّ كل تغيير عبر action تفسّره دالة مركزية واحدة، وهي المُخفّض.

JAVASCRIPT
function requestReducer(state, action) {
  switch (action.type) {
    case "start":
      return { status: "loading", data: null, error: null };
    case "resolve":
      return {
        status: "success",
        data: action.data,
        error: null,
      };
    case "reject":
      return {
        status: "error",
        data: null,
        error: action.error,
      };
    default:
      throw new Error("Unknown action: " + action.type);
  }
}
كل انتقال يعيد بناء حالة كاملة ومتسقة.

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

JSX
function UserProfile() {
  const [state, dispatch] = useReducer(requestReducer, {
    status: "idle",
    data: null,
    error: null,
  });

  async function load(id) {
    dispatch({ type: "start" });
    try {
      const res = await fetch("/api/users/" + id);
      const data = await res.json();
      dispatch({ type: "resolve", data });
    } catch (error) {
      dispatch({ type: "reject", error });
    }
  }

  return <button onClick={() => load(1)}>Load</button>;
}
المكوّن يعبّر عن القصد، والمُخفّض يقرر الحالة.

الاختيار بين useState و useReducer ليس مسألة ذوق. احتفظ بـ useState للقيم البسيطة المستقلة؛ وانتقل إلى useReducer عندما تعتمد الحالة التالية على السابقة أو عندما تتغير عدة حقول معًا. ميزتان إضافيتان: المُعامل الثالث يوفّر تهيئة كسولة (useReducer(reducer, arg, init) لا يُشغّل init إلا في أول تصيير)، وتضمن React أن dispatch يحتفظ بنفس الهوية من تصيير إلى آخر - يمكنك تمريره إلى الأبناء دون useCallback.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. أي إشارة يجب أن توجّهك نحو useReducer بدلاً من عدة استدعاءات useState؟
    • المكوّن يتجاوز مئة سطر
    • عدة قيم حالة تتغير معًا والحالة الجديدة تعتمد على السابقة
    • المكوّن يتلقّى أكثر من ثلاث props
    • الحالة تحتوي على مصفوفة
  2. يُرجع مُخفّض كائن الحالة الذي تلقّاه، مُغيَّرًا في مكانه. ماذا تفعل React؟
    • تقارن المرجعين القديم والجديد، تجدهما متساويين، وتتخطى إعادة التصيير
    • تكتشف التغيير العميق وتعيد تصيير المكوّن
    • ترمي خطأً لأن الحالة مُجمّدة في وضع التطوير
  3. لماذا يمكنك تمرير dispatch إلى ابن مُذاكَر (memoized) دون أي احتياطات خاصة؟
    • لأن React.memo يتجاهل الـ props من نوع الدوال
    • لأن dispatch يُعاد إنشاؤه لكنه متساوٍ بنيويًا في كل تصيير
    • لأن React تضمن أن dispatch يحتفظ بنفس الهوية طوال دورة حياة المكوّن