useReducer در برابر useState: چه زمانی کدام را انتخاب کنیم

در طول این فصل، useReducer را با جزئیات دیدیم. اما useState، که از فصل سوم با آن کار می‌کنیم، همچنان ابزار اصلی و پیش‌فرض برای مدیریت State در بسیاری از کامپوننت‌هاست. در این درس، این دو را مستقیماً مقایسه می‌کنیم تا معیاری روشن برای انتخاب داشته باشیم.

useState برای موارد ساده و مستقل، انتخاب طبیعی‌تری است

وقتی یک State ساده داریم، یا چند State که مستقل از هم تغییر می‌کنند و منطق تغییر هرکدام کوتاه است، useState معمولاً کد کوتاه‌تر و مستقیم‌تری تولید می‌کند:

function LoginForm() {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  // منطق ساده و مستقل برای هرکدام
}

useReducer برای State پیچیده یا به‌هم‌وابسته مناسب‌تر است

وقتی چند فیلد State باید هماهنگ با هم تغییر کنند، یا تعداد Actionهای ممکن روی یک State زیاد و متنوع است، متمرکز کردن این منطق در یک Reducer، خوانایی و قابلیت پیش‌بینی بیشتری می‌دهد:

function formReducer(state, action) {
  switch (action.type) {
    case "fieldChanged":
      return { ...state, [action.field]: action.value };
    case "submitted":
      return { ...state, status: "submitting" };
    case "reset":
      return initialState;
  }
}

یک معیار عملی: تعداد Event Handlerهای مرتبط با یک State

اگر متوجه شدید یک State خاص، توسط بیش از دو یا سه Event Handler مختلف تغییر می‌کند و منطق هرکدام کمی پیچیده است، این نشانه‌ای است که شاید زمان مهاجرت از useState به useReducer رسیده باشد؛ چون در این حالت، Reducer، همه این منطق پراکنده را در یک‌جای واحد و قابل بررسی جمع می‌کند.

useReducer، تست‌پذیری بیشتری هم می‌دهد

چون یک تابع Reducer، جدا از هر کامپوننت خاصی، یک تابع معمولی و Pure است (State و Action می‌گیرد، State جدید می‌دهد)، می‌توان آن را مستقل از رندر شدن هر کامپوننتی، به‌سادگی با ورودی‌های مختلف تست کرد؛ در حالی که تست منطق پخش‌شده در چند Event Handler داخل یک کامپوننت، معمولاً دشوارتر است.

هیچ‌کدام همیشه برتر نیست

نکته مهم این است که useReducer، جایگزین کاملی برای useState نیست و نباید همیشه و برای هر State، پیش‌فرض در نظر گرفته شود. بسیاری از کامپوننت‌ها، حتی در برنامه‌های بزرگ، همچنان بیشتر با useState نوشته می‌شوند و فقط بخش‌های خاصی که واقعاً منطق تغییر پیچیده‌ای دارند، به useReducer منتقل می‌شوند.

می‌توان useState و useReducer را در یک کامپوننت ترکیب کرد

هیچ محدودیتی وجود ندارد که یک کامپوننت فقط از یکی از این دو استفاده کند. رایج است که یک کامپوننت، برای بخشی از State (مثلاً یک فرم پیچیده) از useReducer، و برای بخش دیگری (مثلاً وضعیت باز یا بسته بودن یک Tooltip ساده) از useState استفاده کند.

مثال قابل اجرا

ترکیب useState و useReducer در یک کامپوننت
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

useState برای State ساده و مستقل، انتخاب کوتاه‌تر و طبیعی‌تری است؛ useReducer برای State پیچیده، به‌هم‌وابسته یا با تعداد زیادی نوع تغییر، خوانایی و تست‌پذیری بهتری می‌دهد. این دو در تضاد نیستند و می‌توان آن‌ها را در یک کامپوننت، هرکدام برای بخش مناسب خودشان، با هم ترکیب کرد. در درس بعدی به ترکیب Reducer و Context می‌پردازیم.