طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 242 · أطر الويب
الدرس 6 · الوحدة 2: إتقان إدارة الحالة

تدفق البيانات بـ useReducer

كل قواعد تغيير الحالة في دالة واحدة. ⏱ نحو 30 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تشرح الحالة والفعل والمخفّض والإرسال.
  • تبني حالة مركبة بـ useReducer.
  • تكتب مخفّضًا نقيًّا وتختبره وحده.
  • تختار بين useState و useReducer.

١حين تكثر دوال التغيير

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

function reducer(state, action) {  // تستقبل الحالة الحالية و«فعلًا» يصف ما حدث  // وتعيد الحالة الجديدة (نسخة جديدة، لا تعديل)} const [state, dispatch] = useReducer(reducer, initialState);dispatch({ type: 'add', name: 'قهوة' });   // «أرسل» فعلًا
المصطلحمعناهمثال
الحالة stateالبيانات الحالية{ items: [], total: 0 }
الفعل actionكائن يصف ما حدث، وفيه type على الأقل{ type: 'add', name: 'قهوة', price: 35 }
المخفّض reducerدالة: (حالة، فعل) ← حالة جديدةfunction reducer(state, action)
الإرسال dispatchتسلّم الفعل للمخفّضdispatch({ type: 'clear' })

٢عدّاد بـ useReducer

import { useReducer } from 'react'; function reducer(state, action) {  switch (action.type) {    case 'inc':   return { count: state.count + 1 };    case 'dec':   return { count: Math.max(0, state.count - 1) };    case 'add':   return { count: state.count + action.amount };    case 'reset': return { count: 0 };    default:      throw new Error('فعل غير معروف: ' + action.type);  }} export default function App() {  const [state, dispatch] = useReducer(reducer, { count: 0 });  return (    <div>      <h2>{state.count}</h2>      <button onClick={() => dispatch({ type: 'inc' })}>+1</button>{' '}      <button onClick={() => dispatch({ type: 'dec' })}>-1</button>{' '}      <button onClick={() => dispatch({ type: 'add', amount: 10 })}>+10</button>{' '}      <button onClick={() => dispatch({ type: 'reset' })}>تصفير</button>    </div>  );}

المكوّن لا يعرف كيف تتغير الحالة؛ هو فقط يصف ما حدث. وقاعدة «لا ينزل العدد تحت الصفر» مكتوبة مرة واحدة في المخفّض.

٣سلة مشتريات كاملة

import { useReducer } from 'react';import { cartReducer, initialCart } from './cartReducer.js'; const menu = [  { id: 1, name: 'قهوة عربية', price: 35 },  { id: 2, name: 'تمر سكري', price: 45 },]; export default function App() {  const [cart, dispatch] = useReducer(cartReducer, initialCart);  const total = cart.items.reduce((s, i) => s + i.price * i.qty, 0);   return (    <div>      {menu.map(p => (        <button key={p.id} onClick={() => dispatch({ type: 'add', product: p })}>          أضف {p.name}        </button>      ))}      <ul>        {cart.items.map(i => (          <li key={i.id}>            {i.name} × {i.qty}{' '}            <button onClick={() => dispatch({ type: 'dec', id: i.id })}>−</button>{' '}            <button onClick={() => dispatch({ type: 'remove', id: i.id })}>حذف</button>          </li>        ))}      </ul>      <p>الإجمالي: {total} ريال</p>    </div>  );}

كل قواعد السلة في ملف cartReducer.js: إضافة منتج موجود تزيد كميته، والإنقاص إلى الصفر يحذفه. ويمكن اختبار هذا الملف وحده بلا واجهة.

٤المخفّض دالة نقية: اختبرها بلا واجهة

المخفّض لا يستدعي الخادم، ولا يقرأ الوقت، ولا يغيّر شيئًا خارجه: نفس المدخلات تعطي نفس المخرجات دائمًا. لذلك يسهل اختباره بـ console.log:

import { cartReducer } from './cartReducer.js'; let s = { items: [] };s = cartReducer(s, { type: 'add', product: { id: 1, name: 'قهوة', price: 35 } });s = cartReducer(s, { type: 'add', product: { id: 1, name: 'قهوة', price: 35 } });console.log('بعد إضافتين:', s.items[0].qty);s = cartReducer(s, { type: 'dec', id: 1 });s = cartReducer(s, { type: 'dec', id: 1 });console.log('بعد إنقاصين:', s.items.length, 'عناصر'); export default function App() {  return <p>افتح Console لترى نتيجة الاختبار.</p>;}
useStateuseReducer
المناسبقيم بسيطة مستقلةحالة مركّبة بقواعد تغيير كثيرة
أين المنطق؟داخل معالجات الأحداثفي دالة المخفّض (ملف مستقل)
الاختبارعبر الواجهةالمخفّض وحده بسهولة
ما بعده—الفكرة نفسها أساس Redux (الدرس القادم)

توقّع وتحقّق

ما الذي يعيده المخفّض؟

(state, action) ← state

dispatch({ type: 'clear' }) تعني:

الإرسال.

الدالة النقية:

Pure.

متى نفضّل useReducer على useState؟

تجميع المنطق.

الخلاصة

تحدٍّ برمجي: قائمة مهام بالمخفّض

متوسط

قائمة مهام كل منطقها في مخفّض واحد.

المطلوب:
  • المخفّض يدعم الأفعال: add (بالنص text)، و toggle (بالمعرّف id)، و clearDone (يحذف المنجزة).
  • حقل <input> وزر «أضف»، وكل مهمة <li> تُعلَّم منجزة بالضغط عليها، وتأخذ الصنف done حين تُنجز.
  • زر «احذف المنجزة» يرسل clearDone.
  • استخدم useReducer، ولا تستخدم useState للمهام (يجوز لنص الحقل).
👀 اعرض الناتج المطلوب (وجرّبه)
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء في Console
  • إضافة ثلاث مهام
  • الضغط على مهمة يعلّمها done
  • «احذف المنجزة» يحذفها فقط
  • تستخدم useReducer و dispatch
  • المخفّض فيه الأفعال الثلاثة

💡 تلميح 1
المخفّض يستقبل مصفوفة المهام ويعيد مصفوفة جديدة: [...state, جديد] و map و filter.
💡 تلميح 2
الصنف: className={t.done ? 'done' : ''}

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

import { useReducer, useState } from 'react'; function reducer(state, action) {  switch (action.type) {    case 'add':      return [...state, { id: state.length ? state[state.length - 1].id + 1 : 1, text: action.text, done: false }];    case 'toggle':      return state.map(t => t.id === action.id ? { ...t, done: !t.done } : t);    case 'clearDone':      return state.filter(t => !t.done);    default:      return state;  }} export default function App() {  const [todos, dispatch] = useReducer(reducer, []);  const [text, setText] = useState('');  return (    <div>      <input value={text} onChange={e => setText(e.target.value)} />      <button onClick={() => { if (text.trim()) dispatch({ type: 'add', text }); setText(''); }}>أضف</button>      <ul>        {todos.map(t => (          <li key={t.id} className={t.done ? 'done' : ''} onClick={() => dispatch({ type: 'toggle', id: t.id })}>{t.text}</li>        ))}      </ul>      <button onClick={() => dispatch({ type: 'clearDone' })}>احذف المنجزة</button>    </div>  );}
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس