ویرگول
ورودثبت نام
محمدمهدی بهرامی
محمدمهدی بهرامییه برنامه‌نویس کنجاو در حال کاوش، آزمایش و یادگیری 📚 (سایت: https://bahrami85.ir)
محمدمهدی بهرامی
محمدمهدی بهرامی
خواندن ۵ دقیقه·۵ روز پیش

تفاوت useState و useReducer در ری‌اکت

تفاوت useState و useReducer در ریاکت
تفاوت useState و useReducer در ریاکت

یکی از جالبترین بخشهای ریاکت، هوکها هستن.

هوکها قابلیتهای زیادی به برنامههای ما میدن. اونها حتی به ما کمک میکنن از تکرار یه سری از کدها اجتناب کنیم و یه سری کدهایی در برنامهمون داشته باشیم که قابلیت اشتراکگذاری و استفاده دوباره رو دارن.

در بحث مدیریت وضعیت (State Management)، هوکهای useState و useReducer دو تا از راه حلهای پیشفرضی هستن که ریاکت جلوی پای ما میذاره.

من اول با یه مثال خیلی ساده، useState رو مرور میکنم، بعدش با هم یه مثال پیچیدهتر رو با useState میبینیم. بعد از اون، میرویم سراغ useReducer، تا ببینیم که این هوک چطوری کار ما رو راحتتر میکنه...

خب، شما توی یه صفحه، بخش ورودی ایمیل رو در نظر بگیرین:

function JoinNewsletterForm() { // 👇 ایمیل رو نگه میداره const [email, setEmail] = useState("") return ( <form> <input type="email" // 👇 - مقدار دهی value={email} // 👇 - ثبت تغییرات ={(e) => setEmail(e.target.value)} /> <button type="submit"> Get Bytes </button> </form> ) }

خب این یه مثال ساده هست، نیازی به توضیح خاصی هم نداره!

این برنامهای که پایین عکسش رو میبینین، یه برنامهی «لیست کارها» (Todo List) هست که با React درست شده.

این صفحه «کارها» در برنامه هست.

اگه بخوایم یه نگاهی به کدهای این صفحه بندازیم، با یه همچین چیزی رو به رو میشیم (کد دقیق نیست):

function TasksList() { // اینجا من دارم کارها رو دریافت میکنم const { tasks } = use(TasksContext); return ( <div> {tasks.map((t) => ( <TaskItem key={t.id} title={t.title} id={t.id} isCompleted={t.isCompleted} /> ))} </div> ); }

و توی TasksContext این رو داریم:

const TasksContext = createContext(); function TasksProvider({ children }) { const [tasks, setTasks] = useState([]); // تابع تغییر وضعیت انجامشده / نشده کار const toggleTaskCompleted = (taskId) => { const clonedTasks = [...tasks]; const changedTasks = clonedTasks.map((ct) => ct.id === taskId ? { ...ct, isCompleted: !ct.isCompleted } : ct, ); setTasks(changedTasks); }; // تابع ایجاد کار const createTask = (taskTitle) => { const newTask = { id: uuidv4(), title: taskTitle, isCompleted: false, }; const newTasks = [...tasks, newTask]; setTasks(newTasks); }; // تابع ویرایش کار const editTask = (id, title) => { const editedTasks = [...tasks].map((task) => { if (task.id !== id) return task; return { ...task, title }; }); setTasks(editedTasks); }; // متد حذف کار const deleteTask = (id) => { const editedTasks = tasks.filter((task) => task.id !== id); setTasks(editedTasks); }; return ( <TasksContext value={{ tasks, setTasks, toggleTaskCompleted, createTask, editTask, deleteTask, }} > {children} </TasksContext> ); }

و اینجا ما داریم از useState استفاده میکنیم و کارها رو مدیریت میکنیم.
ولی همونطوری که میبینین، تعداد توابع که با کارها کار میکنن، کم نیست، و میشه اینجا برای منظمتر شدن کد، از هوک useReducer استفاده کنیم.

اولین مرحله اینه که ببینیم ورودیها و خروجیهای useReducer چی هست:

const [ state, dispatch ] = useReducer( reducer, initialState )
  • اولین ورودی یه تابع هست که اسمش reducer هست. کاری که انجام میده اینه که یه دستور میگیره و طبق اون دستور و اطلاعات ضمیمهی اون، تغییرات رو روی وضعیت (state) اعمال میکنه.

  • دومین ورودی یه متغیر یا مقدار میتونه باشه، که حالت اولیه وضعیت هست

  • خروجی این هوک، یه آرایه هست که مقدار اول اون، وضعیت فعلی هست.

  • مقدار دوم آرایه خروجی، تابعی هستش که باهاش به reducer دستور میدیم.

خب، با این حساب بریم کدهای TasksContext رو با useReducer بنویسیم.

خب، اولین قدم اینه که ببینیم استیت کارهای ما چه شکلی هست.

استیت کارهای ما به شکل آرایهای از کارها هستش:

interface Task { id: string; title: string; isCompleted: boolean; }

پس، الان شکل initialState و state رو میدونیم.

const [ state, dispatch ] = useReducer( reducer, initialState ) // ^ آرایه کارها // ^ آرایه کارها // که اگه بخوایم خودمون بنویسیم، میشه این شکلی: const [ tasks, dispatch ] = useReducer( reducer, [] )

حالا باید ببینیم که چه دستورهایی میشه به reducer داد؟

خب، ما هر دستوری که به reducer قراره بدیم، در واقع یه آبجکت هست.

// برای تغییر وضعیت انجامشده / نشده کارها // این دستور رو باید بدیم { type: "toggle-completed", taskId: "flkj2-flkj" } // برای ایجاد کار جدید { type: "create-new", taskTitle: "فیزیک ص ۳۴" } // برای ویرایش کار { type: "edit-task", taskId: "flkj-lkjf", taskTitle: "یادگیری TypeScript" } // برای حذف کار { type: "delete-task", taskId: "jlfkp-fljasd" }

اگه دقت کنین، توی تمام آبجکتهای دستور، ویژگی (property) type (به معنی: نوع) وجود داره.

و ما توی نوشتن خود تابع reducer از پراپرتی type استفاده میکنیم تا بفهمیم که چه کدی رو روی استیت فعلی اجرا کنیم.

و حالا وقت نوشتن خود تابع reducer هست که این دستورات رو ورودی میگیره، محاسبات رو انجام میده، استیت رو ویرایش میکنه و استیت ویرایششده و جدید رو بر میگردونه:

// tasks: استیت فعلی کارها // action: همون آبجکت دستور ما هست function tasksReducer(tasks, action) { // اول پراپرتی تایپ رو چک میکنم، بعد منطق مربوطه رو براش مینویسم if (action.type === "toggle-completed") { // اینجا منطق تغییر وضعیت انجامشدن کار رو مینویسیم const clonedTasks = [...tasks]; const changedTasks = clonedTasks.map((ct) => ct.id === taskId ? { ...ct, isCompleted: !ct.isCompleted } : ct, ); // آخر سر، من چیزی که میخوام جای استیت فعلی بشینه رو بر میگردونم return changedTasks; } // بقیه منطقها... }

و حالا ما این رو هم در هوک useReducer مینویسیم:

const [ tasks, dispatch ] = useReducer( tasksReducer, [] ) // ^ طبق چیزی که تعریف کردیم

مرحله بعد، آپدیتکردن خود TasksContext هست.

function TasksProvider({ children }) { const [tasks, dispatch] = useReducer(tasksReducer, []); return ( <TasksContext value={{ tasks, dispatch, }} > {children} </TasksContext> ); }

و حالا... جای جالبش رسید.
اینجا رو نگاه کنین 👇 بخش مشخصات کار هست:

بیاین یه نگاهی به کد دکمه «حذف کن» بندازیم:

(کد قبل، با useState):

const { deleteTask } = use(TasksContext); <Btn title="حذف کن" color="danger" IconEnd={TrashIcon} ={() => deleteTask(id)} />

(کد جدید، با useReducer):

const { dispatch } = use(TasksContext); <Btn title="حذف کن" color="danger" IconEnd={TrashIcon} ={() => dispatch({ type: "delete-task", taskId: id, })} />

شاید به نظرتون بیاد که «این که اصلا فرق خاصی نکرده، حتی کدها بیشتر هم شده»
ولی،
وقتی کارتون به نوشتن برنامههای بزرگتر میرسه،
وقتی که استیتهای برنامه خیلی پیچیدهتر میشه،
و منطق و تعداد کارهایی که باید روی استیت انجام بشه افزایش پیدا میکنه،
وقتی که پروژه رو باید دیباگ کنین چون یه اشتباه کوچولو کردین،
متوجه میشین که useReducer،
چون تمام منطقهای مربوط به استیت، توی یه جا جمع شده (توی reducer)
پس، گسترش و توسعه اون و دیباگکردن reducer راحتتر هست!

ولی باید تعادل رو هم حفظ کرد. نباید تند و تند و بیجا از useReducer استفاده کرد.
به نظر من، باید هر جایی که دیدین منطقهای مربوط به استیت، داره خیلی زیاد میشه،
اونوقت به نظر من موقع خوبی برای استفادهکردن از useReducer هست.

خب، یه جمع بندی بکنیم 👇

(حوصلهاش رو ندارم، میخوام برم بخوابم فردا برم سرکار... 😁😂)

امیدوارم که این مینی-مقاله بدردتون خورده باشه و چیز بدرد بخوری یاد گرفته باشین ازش...

تشکر از شما که برای خوندن مقاله من وقت گذاشتین 🌱

ری‌اکتusestateusereducerبرنامه‌نویسیفرانت‌اند
۰
۰
محمدمهدی بهرامی
محمدمهدی بهرامی
یه برنامه‌نویس کنجاو در حال کاوش، آزمایش و یادگیری 📚 (سایت: https://bahrami85.ir)
شاید از این پست‌ها خوشتان بیاید