از زمانی که React Hook ها منتشر شده اند، امکان استفاده از state ها و side-effect ها در function component ها فراهم شده است. useState و useReducer دو هوک پر کاربرد در مدیریت state ها هستند. در حقیقت useReducer hook یک روش جایگزین برای useState است البته در شرایطی که مدیریت state پیچیده باشد. در این مقاله useReducer را معرفی می کنیم و بررسی می کنیم در چه سناریوهایی بهتر است از useReducer به جای useState استفاده کنید. پیشنهاد می شود قبل از مطالعه این مقاله، مفهوم reducer در جاوااسکریپت را مطالعه کنید. چرا که مفاهیم پایه ای مربوط به reducer در جاوااسکریپت را در آن مقاله مورد بررسی قرار داده ایم. همچنین برای آموزش های بیشتر ری اکت می توانید از طریق دوره های آموزش ری اکت اقدام کنید.
useReducer چیست؟
useReducer در حقیقت به عنوان modern state management در React شناخته می شود. این مفهوم اولین بار در Redux معرفی شد و سپس توسط تیم react نیز مورد استفاده قرار گرفت. Reducer در حقیقت تابعی است که دو آرگومان به نام های state و action از ورودی می گیرد و بسته به نوع action، عملیاتی بر روی state انجام می دهد و یک state جدید به عنوان خروجی بر می گرداند. خروجی useReducer یک state جدید و یک تابع به نام dispatch است که برای فراخوانی reducer function با یک action خاص مورد استفاده قرار می گیرد. شاید یکی از بهترین سناریوها برای استفاده از useReducer به جای useState، فراخوانی api ها باشد.
چگونه از useReducer در فراخوانی api ها استفاده کنیم؟
قبل از پیاده سازی useReducer اجازه دهید پیاده سازی را با useState انجام دهیم تا ببینیم چه مشکلاتی در استفاده از useState در این سناریو به وجود می آید. کد زیر را در نظر بگیرید:
const FastFoods = () => {
const [fastFoodItems, setFastFoodItems] = useState();
const [loading, setLoading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
const fastFoods = async () => {
let response = await fetch(
"https://react-mini-projects-api.classbon.com/Fastfood/list"
);
if (response.status === 200) {
const data = await response.json();
setFastFoodItems(data);
setError(false);
setLoading(false);
return;
}
setError(response.error);
setLoading(false);
};
fastFoods();
}, []);
return (
<div>
{loading ? (
<p>Loading...</p>
) : error ? (
<p>{error}</p>
) : (
<ul>
{fastFoodItems.map((fastfood) => (
<li key={fastfood.id}>
<h4>{fastfood.name}</h4>
</li>
))}
</ul>
)}
</div>
);
};
export default FastFoods;
همانطور که می بینید از سه state مجزا استفاده کرده ایم. واقعیت این است در یک اپلیکیشن کامل تعداد این state ها بیشتر هم می شود. اما برای شروع همین سه state کافی است. آنچه مشخص است این سه state به هم وابستگی دارند. یعنی در حین فراخوانی api تا قبل از رسیدن پاسخ نیاز است loading فعال شود و پس از رسیدن پاسخ اگر api با موفقیت انجام شده باشد که پاسخ را می گیریم در غیر این صورت باید state مربوط به error را فعال کنیم و نمایش دهیم. اگر ما فقط از useState استفاده کنیم دنبال کردن این state ها از آنجا که مستقل از هم هستند دشوار و البته مستعد خطاست. ما باید مراقب باشیم که state ها را در زمان درست خود بروزرسانی کنیم.
برای حل این مشکلات روش بهتر استفاده از useReducer است. کد فوق را با استفاده از useReducer به شکل زیر بازنویسی می کنیم:
const ACTIONS = {
CALL_API: "call-api",
SUCCESS: "success",
ERROR: "error",
};
const fastFoodItemsReducer = (state, action) => {
switch (action.type) {
case ACTIONS.CALL_API: {
return {
...state,
loading: true,
};
}
case ACTIONS.SUCCESS: {
return {
...state,
loading: false,
fastfoodItems: action.data,
};
}
case ACTIONS.ERROR: {
return {
...state,
loading: false,
error: action.error,
};
}
}
};
const initialState = {
fastfoodItems: [],
loading: false,
error: null,
};
const FastFoods = () => {
debugger;
const [state, dispatch] = useReducer(fastFoodItemsReducer, initialState);
const { fastfoodItems, loading, error } = state;
useEffect(() => {
dispatch({ type: ACTIONS.CALL_API });
const fastFoods = async () => {
let response = await fetch(
"https://react-mini-projects-api.classbon.com/Fastfood/list"
);
if (response.status === 200) {
const data = await response.json();
dispatch({ type: ACTIONS.SUCCESS, data: data });
return;
}
dispatch({ type: ACTIONS.ERROR, error: response.error });
};
fastFoods();
}, []);
return (
<div>
{loading ? (
<p>Loading...</p>
) : error ? (
<p>{error}</p>
) : (
<ul>
{fastfoodItems.map((fastfood) => (
<li key={fastfood.id}>
<h4>{fastfood.name}</h4>
</li>
))}
</ul>
)}
</div>
);
};
export default FastFoods;
در اینجا ما از dispatch function برای فراخوانی reducer function استفاده می کنیم. در reducer از switch case استفاده کرده ایم تا تعیین کنیم چه action ی قرار است روی state انجام شود. شاید در نگاه اول تصور کنید کد شما طولانی تر و پیچیده تر شده است. اما مفهوم آن ساده تر شده است.
useReducer دو آرگومان نیاز دارد. Reducer function و state اولیه. در حقیقت reducer function تمامی عملیات را بر روی همین state ورودی انجام می دهد و یک state جدید بر می گرداند. اما شاید بپرسید مزیت این کار چیست؟
- State ها فقط در یک تابع و در یک محل آپدیت می شوند و این کار بر مبنای action ی است که به reducer function ارسال می شود.
زمانی که ما action را ارسال می کنیم در حقیقت بیان می کنیم باید چه عملیاتی بر روی state قبلی انجام شود. با این کار می توانیم تضمین کنیم که همه state ها با هم سازگار هستند و شانس اینکه آپدیت اشتباهی بر روی state انجام شود به شدت کاهش می یابد. - مدیریت state های پیچیده ساده می شود
از آنجا که فقط یک تابع state را آپدیت می کند، مدیریت state های پیچیده که شامل آرایه ها و آبجکت ها می شود، تسهیل می شود.
- قابلیت تست پذیری ارتقا می یابد
Reducer ها توابعی هستند که عملیات را بر مبنای action های از پیش تعیین شده انجام می دهند. در نتیجه، خروجی آن ها کاملا قابل پیش بینی است. به عبارت بهتر هیچ side effect ی ندارند و همواره به ازای هر آرگومانی، خروجی یکسانی را بر می گردانند.
چه زمانی از useReducer و چه زمانی از useState استفاده کنیم؟
به طور کلی استفاده از useReducer نسبت به useState ارجحیت دارد اما نه در همه حال. چنانچه در حال پیاده سازی سناریویی ساده هستید و state پیچیده ای ندارید بهتر است از useState استفاده کنید. چرا که useReducer در چنین حالاتی فقط پیچیدگی را بیشتر می کند و هیچ ارزش افزوده ای به کار شما نمی دهد. به طور کلی در دو حال استفاده از useReducer نسبت به useState اولویت دارد:
- اگر state هایی دارید که به هم وابستگی دارند.
- اگر state شما پیچیده باشد. مثلا ترکیبی از آرایه ها و آبجکت ها داشته باشد.
امیدوارم که این قواعد در تصمیم گیری تان برای انتخاب useState یا useReducer کمک کند. اگر معیارهای دیگری برای انتخاب بین این دو می دانید لطفا در بخش کامنت ها مطرح کنید. همچنین برای استفاده واقعی تر از useReducer می توانید در دوره های آموزش React به خصوص پروژه فست فود کاتالوگ شرکت کنید.