مقدمه

در درس‌های گذشته، با useReducer برای مدیریت stateهای پیچیده و با useContext برای جلوگیری از prop drilling آشنا شدیم. اکنون، ما این دو هوک قدرتمند را با هم ترکیب خواهیم کرد تا یک الگوی مدیریت وضعیت سراسری (global state management) بسیار تمیز و مقیاس‌پذیر بسازیم.

این الگو به ما اجازه می‌دهد تا منطق state خود را در یک reducer متمرکز کرده و سپس هم خود state و هم تابع dispatch را از طریق context در اختیار تمام کامپوننت‌هایی که به آن نیاز دارند، قرار دهیم. این رویکرد، پایه و اساس بسیاری از کتابخانه‌های مدیریت وضعیت مانند Redux است، اما ما می‌توانیم آن را به صورت بومی و تنها با استفاده از هوک‌های داخلی React پیاده‌سازی کنیم.

پیاده‌سازی گام به گام

بیایید مثال لیست کارها (Todo List) از درس useReducer را بازسازی کنیم تا state و dispatch آن از طریق context قابل دسترس باشند.

۱. ایجاد دو Context مجزا

یک رویه بهینه این است که برای state و تابع dispatch، دو context جداگانه بسازیم. این کار به React اجازه می‌دهد تا بهینه‌سازی‌های بهتری را انجام دهد. کامپوننت‌هایی که فقط به dispatch نیاز دارند، با تغییر state دوباره رندر نخواهند شد.

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

export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);

۲. ایجاد Provider در کامپوننت والد

حالا در کامپوننت والد (مانند App.js)، ما useReducer را فراخوانی کرده و state و dispatch را از طریق Providerهای مربوطه فراهم می‌کنیم.

Copy Icon JAVASCRIPT (React)
import { useReducer } from 'react';
import { TasksContext, TasksDispatchContext } from './TasksContext.js';
import todosReducer from './todosReducer.js';

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

  return (
    <TasksContext.Provider value={tasks}>
      <TasksDispatchContext.Provider value={dispatch}>
        <!-- All child components can now access tasks and dispatch -->
        <TaskApp />
      </TasksDispatchContext.Provider>
    </TasksContext.Provider>
  );
}

استفاده از Context در کامپوننت‌های فرزند

اکنون هر کامپوننت فرزندی، صرف نظر از عمق آن در درخت، می‌تواند با استفاده از useContext به state (برای خواندن) و dispatch (برای به‌روزرسانی) دسترسی پیدا کند، بدون اینکه نیاز به prop drilling داشته باشیم.

Copy Icon JAVASCRIPT (React)
import { useContext } from 'react';
import { TasksContext, TasksDispatchContext } from './TasksContext.js';

function TaskList() {
  const tasks = useContext(TasksContext);
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <Task task={task} />
        </li>
      ))}
    </ul>
  );
}

function Task({ task }) {
  const dispatch = useContext(TasksDispatchContext);
  // ... JSX for a single task with edit/delete buttons ...
  // onClick={() => dispatch({ type: 'deleted', id: task.id })}
}

در این مثال، کامپوننت TaskList تنها به state (tasks) نیاز دارد، بنابراین TasksContext را مصرف می‌کند. کامپوننت Task تنها نیاز به تغییر state دارد، بنابراین TasksDispatchContext را مصرف می‌کند. این جداسازی به بهینه‌سازی عملکرد کمک می‌کند.

در این درس، با ترکیب useReducer و useContext، یک الگوی بسیار قدرتمند و تمیز برای مدیریت وضعیت سراسری در اپلیکیشن‌های React ساختیم. این الگو به ما کمک می‌کند تا منطق state را از کامپوننت‌های UI جدا کرده و داده‌ها را به صورت کارآمد و بدون prop drilling در سراسر اپلیکیشن به اشتراک بگذاریم.

این رویکرد، سنگ بنای ساخت اپلیکیشن‌های React بزرگ و مقیاس‌پذیر است. در درس بعدی، به «الگوهای مدیریت وضعیت در پروژه‌های بزرگ» خواهیم پرداخت و این الگو را با راه‌حل‌های کتابخانه‌ای مانند Redux مقایسه خواهیم کرد.