مقدمه
در درس قبل با اصول اولیه هوک useReducer آشنا شدیم. دیدیم که این هوک به ما اجازه میدهد تا منطق
بهروزرسانی state را در یک تابع reducer جداگانه متمرکز کنیم. این الگو به خصوص زمانی قدرتمند
میشود که با stateهای پیچیده و عملیاتهای متنوع سروکار داریم و جداسازی منطق state از کامپوننت
UI، خوانایی، قابلیت نگهداری و تستپذیری کد را به شدت افزایش میدهد.
ارسال داده به reducer با Payload
تاکنون، «اکشن» (action)هایی که به تابع dispatch ارسال کردهایم، تنها شامل یک پراپرتی type
بودهاند. اما اغلب اوقات، ما نیاز داریم که همراه با اکشن، دادههای بیشتری را نیز به reducer
ارسال کنیم تا بر اساس آن دادهها، state را بهروزرسانی کند. طبق یک قرارداد رایج، این دادههای
اضافی در یک پراپرتی به نام payload در داخل شیء اکشن قرار میگیرند.
یک مثال: مدیریت لیست کارها (Todo List)
بیایید یک reducer برای مدیریت یک لیست از کارها بسازیم. این reducer باید قابلیت افزودن یک کار
جدید، حذف یک کار، و تغییر وضعیت انجام شدن آن را داشته باشد.
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 را در یک کامپوننت برای مدیریت لیست کارها استفاده کنیم.
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
});
}
}
در کامپوننت TaskApp، ما state مربوط به tasks را با useReducer مدیریت میکنیم. ما سه تابع
handler تعریف کردهایم که هر کدام یک نوع اکشن متفاوت را به همراه دادههای لازم، dispatch
میکنند.
این الگو بسیار تمیز و مقیاسپذیر است. تمام منطق پیچیده مربوط به افزودن، ویرایش و حذف کارها در
داخل reducer کپسوله شده است و کامپوننت ما تنها مسئولیت رندر کردن UI و dispatch کردن
اکشنها را بر عهده دارد.
در این درس، با جزئیات بیشتری به نحوه مدیریت stateهای پیچیده با استفاده از reducer پرداختیم.
دیدیم که چگونه میتوان با ارسال دادههای اضافی (payload) در اکشنها، منطقهای بهروزرسانی متنوعی
را پیادهسازی کرد و چگونه این الگو به جداسازی کامل منطق state از UI کمک میکند.
این الگو به خصوص برای stateهایی که در چندین کامپوننت مختلف به اشتراک گذاشته میشوند، بسیار
قدرتمند است. در درس بعدی، به «معرفی context و کاربرد آن» خواهیم پرداخت و یاد میگیریم که چگونه
state را بدون نیاز به پاس دادن props در لایههای تودرتو، به صورت سراسری در اپلیکیشن خود در
دسترس قرار دهیم.