چه زمانی از useReducer به جای useState  استفاده کنیم؟
عباس سپهوند
عباس سپهوند
  • 1401/07/02
  • 20 دقیقه
  • 0 نظر

چه زمانی از useReducer به جای useState استفاده کنیم؟

در این مقاله به بررسی یکی از سناریوهایی می پردازیم که بهترین گزینه برای استفاده از useReducer به جای useState است

از زمانی که 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 اولویت دارد:

  1. اگر state هایی دارید که به هم وابستگی دارند.
  2. اگر state شما پیچیده باشد. مثلا ترکیبی از آرایه ها و آبجکت ها داشته باشد.

امیدوارم که این قواعد در تصمیم گیری تان برای انتخاب useState یا useReducer کمک کند. اگر معیارهای دیگری برای انتخاب بین این دو می دانید لطفا در بخش کامنت ها مطرح کنید. همچنین برای استفاده واقعی تر از useReducer می توانید در دوره های آموزش React به خصوص پروژه فست فود کاتالوگ شرکت کنید.

دیدگاه

برای ارسال دیدگاه های خود ابتدا وارد شوید یا ثبت نام کنید

ورود یا ثبت نام
عباس سپهوند
عباس سپهوند

برنامه نویس و توسعه دهنده نرم افزار

مشاهده پروفایل
5 مقاله اخیر

آموزش React: راهنمای کامل useCallback

عباس سپهوند
زمان مطالعه: 40

آموزش React: راهنمای کامل useEffect

عباس سپهوند
زمان مطالعه: 25

آموزش React: راهنمای کامل Ref ها در React

عباس سپهوند
زمان مطالعه: 15
مشاهده همه مقالات