مقدمه
در درسهای گذشته، با 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 دوباره رندر نخواهند شد.
JAVASCRIPT (React)
import { createContext } from 'react';
export const TasksContext = createContext(null);
export const TasksDispatchContext = createContext(null);
۲. ایجاد Provider در کامپوننت والد
حالا در کامپوننت والد (مانند App.js)، ما useReducer را فراخوانی کرده و state و dispatch
را از طریق Providerهای مربوطه فراهم میکنیم.
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}>
<TaskApp />
</TasksDispatchContext.Provider>
</TasksContext.Provider>
);
}
استفاده از Context در کامپوننتهای فرزند
اکنون هر کامپوننت فرزندی، صرف نظر از عمق آن در درخت، میتواند با استفاده از useContext به
state (برای خواندن) و dispatch (برای بهروزرسانی) دسترسی پیدا کند، بدون اینکه نیاز به prop
drilling داشته باشیم.
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);
}
در این مثال، کامپوننت TaskList تنها به state (tasks) نیاز دارد، بنابراین TasksContext را
مصرف میکند. کامپوننت Task تنها نیاز به تغییر state دارد، بنابراین TasksDispatchContext را
مصرف میکند. این جداسازی به بهینهسازی عملکرد کمک میکند.
در این درس، با ترکیب useReducer و useContext، یک الگوی بسیار قدرتمند و تمیز برای مدیریت
وضعیت سراسری در اپلیکیشنهای React ساختیم. این الگو به ما کمک میکند تا منطق state را از
کامپوننتهای UI جدا کرده و دادهها را به صورت کارآمد و بدون prop drilling در سراسر اپلیکیشن
به اشتراک بگذاریم.
این رویکرد، سنگ بنای ساخت اپلیکیشنهای React بزرگ و مقیاسپذیر است. در درس بعدی، به «الگوهای
مدیریت وضعیت در پروژههای بزرگ» خواهیم پرداخت و این الگو را با راهحلهای کتابخانهای مانند
Redux مقایسه خواهیم کرد.