مقدمه

در درس قبل با اصول اولیه هوک useReducer آشنا شدیم. دیدیم که این هوک به ما اجازه می‌دهد تا منطق به‌روزرسانی state را در یک تابع reducer جداگانه متمرکز کنیم. این الگو به خصوص زمانی قدرتمند می‌شود که با stateهای پیچیده و عملیات‌های متنوع سروکار داریم و جداسازی منطق state از کامپوننت UI، خوانایی، قابلیت نگهداری و تست‌پذیری کد را به شدت افزایش می‌دهد.

ارسال داده به reducer با Payload

تاکنون، «اکشن» (action)هایی که به تابع dispatch ارسال کرده‌ایم، تنها شامل یک پراپرتی type بوده‌اند. اما اغلب اوقات، ما نیاز داریم که همراه با اکشن، داده‌های بیشتری را نیز به reducer ارسال کنیم تا بر اساس آن داده‌ها، state را به‌روزرسانی کند. طبق یک قرارداد رایج، این داده‌های اضافی در یک پراپرتی به نام payload در داخل شیء اکشن قرار می‌گیرند.

یک مثال: مدیریت لیست کارها (Todo List)

بیایید یک reducer برای مدیریت یک لیست از کارها بسازیم. این reducer باید قابلیت افزودن یک کار جدید، حذف یک کار، و تغییر وضعیت انجام شدن آن را داشته باشد.

Copy Icon JAVASCRIPT (React)
const initialTodos = [
  { id: 1, text: 'Learn React', done: true },
  { id: 2, text: 'Build a project', done: false },
];

function todosReducer(todos, action) {
  switch (action.type) {
    case 'added': {
      return [
        ...todos,
        { id: action.id, text: action.text, done: false }
      ];
    }
    case 'changed': {
      return todos.map(t => {
        if (t.id === action.task.id) {
          return action.task;
        } else {
          return t;
        }
      });
    }
    case 'deleted': {
      return todos.filter(t => t.id !== action.id);
    }
    default: {
      throw new Error('Unknown action: ' + action.type);
    }
  }
}

در این reducer، ما به جای استفاده از یک پراپرتی payload، داده‌های اضافی را مستقیماً به شیء اکشن اضافه کرده‌ایم (مانند action.id و action.text). این روش کاملاً معتبر است. در شاخه added، ما یک کار جدید به آرایه اضافه می‌کنیم. در changed، با استفاده از map، کار مورد نظر را با نسخه جدید آن جایگزین می‌کنیم. و در deleted، با استفاده از filter، کار مورد نظر را از آرایه حذف می‌کنیم.

استفاده از reducer در کامپوننت

اکنون می‌توانیم این reducer را در یک کامپوننت برای مدیریت لیست کارها استفاده کنیم.

Copy Icon JAVASCRIPT (React)
import { useReducer } from 'react';

function TaskApp() {
  const [tasks, dispatch] = useReducer(todosReducer, initialTodos);

  function handleAddTask(text) {
    dispatch({
      type: 'added',
      id: nextId++,
      text: text,
    });
  }

  function handleChangeTask(task) {
    dispatch({
      type: 'changed',
      task: task
    });
  }

  function handleDeleteTask(taskId) {
    dispatch({
      type: 'deleted',
      id: taskId
    });
  }

  // ... return JSX to display tasks and forms ...
}

در کامپوننت TaskApp، ما state مربوط به tasks را با useReducer مدیریت می‌کنیم. ما سه تابع handler تعریف کرده‌ایم که هر کدام یک نوع اکشن متفاوت را به همراه داده‌های لازم، dispatch می‌کنند.

این الگو بسیار تمیز و مقیاس‌پذیر است. تمام منطق پیچیده مربوط به افزودن، ویرایش و حذف کارها در داخل reducer کپسوله شده است و کامپوننت ما تنها مسئولیت رندر کردن UI و dispatch کردن اکشن‌ها را بر عهده دارد.

در این درس، با جزئیات بیشتری به نحوه مدیریت stateهای پیچیده با استفاده از reducer پرداختیم. دیدیم که چگونه می‌توان با ارسال داده‌های اضافی (payload) در اکشن‌ها، منطق‌های به‌روزرسانی متنوعی را پیاده‌سازی کرد و چگونه این الگو به جداسازی کامل منطق state از UI کمک می‌کند.

این الگو به خصوص برای stateهایی که در چندین کامپوننت مختلف به اشتراک گذاشته می‌شوند، بسیار قدرتمند است. در درس بعدی، به «معرفی context و کاربرد آن» خواهیم پرداخت و یاد می‌گیریم که چگونه state را بدون نیاز به پاس دادن props در لایه‌های تودرتو، به صورت سراسری در اپلیکیشن خود در دسترس قرار دهیم.