بررسی مدیریت حالت یا استیت در ری اکت و نکست جی اس

مدیریت حالت (State Management) در ریاکت (React) و نکستجیاس (Next.js) یکی از مباحث کلیدی در توسعه برنامههای وب مدرن است. در ادامه، به صورت جامع و ساختاریافته به این موضوع میپردازیم و روشهای مختلف مدیریت حالت را با مثالهای ساده توضیح میدهم.


 1. مدیریت حالت در ریاکت (React)

ریاکت یک کتابخانه جاوااسکریپت برای ساخت رابط کاربری است و مدیریت حالت یکی از بخشهای اصلی آن است. حالت (State) دادهای است که در طول زمان تغییر میکند و بر رندر شدن کامپوننتها تأثیر میگذارد.

الف. استفاده از useState (مدیریت حالت محلی)

useState یک هوک داخلی ریاکت است که برای مدیریت حالت در کامپوننتهای تابعی استفاده میشود.


مثال:

```jsx

import React, { useState } from 'react';


function Counter() {

  const [count, setCount] = useState(0);


  return (

    <div>

      <p>شمارنده: {count}</p>

      <button ={() => setCount(count + 1)}>افزایش</button>

    </div>

  );

}

export default Counter;

```

- useState(0): مقدار اولیه حالت را تعیین میکند (اینجا 0).

- count: مقدار فعلی حالت.

- setCount: تابعی برای بهروزرسانی حالت.

- نکته: همیشه از setCount برای تغییر حالت استفاده کنید، نه تغییر مستقیم count.


ب. استفاده از useReducer (برای حالتهای پیچیدهتر)

وقتی حالتها پیچیدهتر میشوند (مثلاً چندین مقدار مرتبط یا منطق پیچیده)، از useReducer استفاده میکنیم.


مثال:

```jsx

import React, { useReducer } from 'react';


const initialState = { count: 0 };


function reducer(state, action) {

  switch (action.type) {

    case 'increment':

      return { count: state.count + 1 };

    case 'decrement':

      return { count: state.count - 1 };

    default:

      return state;

  }

}


function Counter() {

  const [state, dispatch] = useReducer(reducer, initialState);


  return (

    <div>

      <p>شمارنده: {state.count}</p>

      <button ={() => dispatch({ type: 'increment' })}>افزایش</button>

      <button ={() => dispatch({ type: 'decrement' })}>کاهش</button>

    </div>

  );

}

export default Counter;

```

- reducer: تابعی که منطق بهروزرسانی حالت را تعریف میکند.

- dispatch: برای ارسال اکشنها به reducer استفاده میشود.

- مزیت: مناسب برای مدیریت حالتهای پیچیده با منطقهای شرطی.


ج. استفاده از Context API (برای حالتهای جهانی)

وقتی نیاز دارید حالت بین چندین کامپوننت به اشتراک گذاشته شود، از Context API استفاده میشود.


مثال:

```jsx

import React, { createContext, useContext, useState } from 'react';


// ایجاد Context

const ThemeContext = createContext();


function App() {

  const [theme, setTheme] = useState('light');


  return (

    <ThemeContext.Provider value={{ theme, setTheme }}>

      <Toolbar />

    </ThemeContext.Provider>

  );

}


function Toolbar() {

  const { theme, setTheme } = useContext(ThemeContext);


  return (

    <div>

      <p>تم فعلی: {theme}</p>

      <button ={() => setTheme(theme === 'light' ? 'dark' : 'light')}>

        تغییر تم

      </button>

    </div>

  );

}

export default App;

```

- createContext: یک Context جدید ایجاد میکند.

- Provider: حالت را به کامپوننتهای فرزند ارائه میدهد.

- useContext: برای دسترسی به حالت در کامپوننتهای فرزند.


د. کتابخانههای مدیریت حالت (مانند Redux یا Zustand)

برای برنامههای بزرگ، کتابخانههای خارجی مثل Redux، Zustand یا Recoil استفاده میشوند.

- Redux: یک فروشگاه مرکزی برای حالتهای برنامه ایجاد میکند.

  - نصب: npm install @reduxjs/toolkit react-redux

  - مثال ساده:

    ```jsx

    import { configureStore } from '@reduxjs/toolkit';

    import { Provider, useDispatch, useSelector } from 'react-redux';

    import counterReducer from './counterSlice';


    const store = configureStore({

      reducer: {

        counter: counterReducer,

      },

    });


    function Counter() {

      const count = useSelector((state) => state.counter.count);

      const dispatch = useDispatch();


      return (

        <div>

          <p>شمارنده: {count}</p>

          <button ={() => dispatch({ type: 'counter/increment' })}>

            افزایش

          </button>

        </div>

      );

    }


    export default function App() {

      return (

        <Provider store={store}>

          <Counter />

        </Provider>

      );

    }

    ```

- Zustand: سبکتر و سادهتر از Redux.

  - نصب: npm install zustand

  - مثال:

    ```jsx

    import create from 'zustand';


    const useStore = create((set) => ({

      count: 0,

      increment: () => set((state) => ({ count: state.count + 1 })),

    }));


    function Counter() {

      const { count, increment } = useStore();

      return (

        <div>

          <p>شمارنده: {count}</p>

          <button ={increment}>افزایش</button>

        </div>

      );

    }

    ```


 2. مدیریت حالت در نکستجیاس (Next.js)

نکستجیاس یک فریمورک مبتنی بر ریاکت است که قابلیتهای اضافی مانند رندر سمت سرور (SSR) و تولید استاتیک (SSG) را ارائه میدهد. مدیریت حالت در نکستجیاس مشابه ریاکت است، اما چند نکته خاص وجود دارد.


الف. مدیریت حالت محلی

مانند ریاکت، میتوانید از useState و useReducer در کامپوننتهای نکستجیاس استفاده کنید.


مثال:

```jsx

import { useState } from 'react';


export default function CounterPage() {

  const [count, setCount] = useState(0);


  return (

    <div>

      <p>شمارنده: {count}</p>

      <button ={() => setCount(count + 1)}>افزایش</button>

    </div>

  );

}

```


ب. مدیریت حالت جهانی

برای حالتهای جهانی، میتوانید از Context API یا کتابخانههایی مثل Redux و Zustand استفاده کنید، مشابه ریاکت.


ج. استفاده از API Routes برای مدیریت دادههای سرور

نکستجیاس امکان ایجاد API Routes را فراهم میکند که میتوانید از آنها برای دریافت یا ذخیره دادهها استفاده کنید.


مثال:

ایجاد یک API Route در pages/api/counter.js:

```javascript

export default function handler(req, res) {

  if (req.method === 'GET') {

    res.status(200).json({ count: 10 });

  }

}

```


دریافت داده در کامپوننت:

```jsx

import { useState, useEffect } from 'react';


export default function CounterPage() {

  const [count, setCount] = useState(0);


  useEffect(() => {

    fetch('/api/counter')

      .then((res) => res.json())

      .then((data) => setCount(data.count));

  }, []);


  return <div>شمارنده: {count}</div>;

}

```


د. استفاده از SWR یا React Query برای دادههای سمت سرور

برای مدیریت دادههای دریافتی از سرور، کتابخانههایی مثل SWR یا React Query بسیار مناسب هستند.


مثال با SWR:

- نصب: npm install swr

- استفاده:

  ```jsx

  import useSWR from 'swr';


  const fetcher = (url) => fetch(url).then((res) => res.json());


  export default function CounterPage() {

    const { data, error } = useSWR('/api/counter', fetcher);


    if (error) return <div>خطا در بارگذاری</div>;

    if (!data) return <div>در حال بارگذاری...</div>;


    return <div>شمارنده: {data.count}</div>;

  }

  ```

- مزیت SWR: کش کردن دادهها، بهروزرسانی خودکار و مدیریت خطاها.


ه. نکات خاص نکستجیاس

- رندر سمت سرور (SSR): اگر از getServerSideProps استفاده میکنید، میتوانید دادههای اولیه را به کامپوننت ارسال کنید:

  ```jsx

  export async function getServerSideProps() {

    return { props: { initialCount: 10 } };

  }


  export default function CounterPage({ initialCount }) {

    const [count, setCount] = useState(initialCount);


    return (

      <div>

        <p>شمارنده: {count}</p>

        <button ={() => setCount(count + 1)}>افزایش</button>

      </div>

    );

  }

  ```

- تولید استاتیک (SSG): با getStaticProps میتوانید دادههای ثابت را در زمان ساخت لود کنید.


3. نکات بهترین روشها (Best Practices)

1. حداقل استفاده از حالت جهانی: تا جایی که ممکن است از useState و useReducer برای حالتهای محلی استفاده کنید و فقط در صورت نیاز به Context یا کتابخانههای مدیریت حالت بروید.

2. عملکرد بهینه: از useMemo و useCallback برای جلوگیری از رندر غیرضروری استفاده کنید.

   ```jsx

   const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

   ```

3. تقسیمبندی منطقی: حالتهای مرتبط را در یک Context یا Store نگه دارید و از پراکندگی حالتها جلوگیری کنید.

4. تستپذیری: منطق مدیریت حالت (مثل reducerها) را جدا از رابط کاربری بنویسید تا تست کردن آسانتر شود.

5. مدیریت دادههای سرور: در نکستجیاس، از SWR یا React Query برای دادههای پویا و از SSR/SSG برای دادههای ثابت استفاده کنید.

 4. مقایسه ابزارهای مدیریت حالت

در ادامه، ابزارهای مختلف مدیریت حالت در ریاکت و نکستجیاس به صورت متنی مقایسه شدهاند تا به راحتی قابل استفاده باشند. هر ابزار با توضیح مزایا و معایب آن شرح داده شده است:

1. useState  

   - مزایا:  

     ساده و آسان برای استفاده.  

     مناسب برای مدیریت حالتهای محلی و ساده در کامپوننتهای کوچک.  

     نیازی به نصب کتابخانه اضافی ندارد، چون بخشی از ریاکت است.  

   - معایب:  

     برای حالتهای پیچیده یا زمانی که چندین مقدار به هم وابسته هستند، مناسب نیست.  

     مدیریت حالتهای بزرگ و گسترده با useState میتواند کد را نامرتب کند.  


2. useReducer  

   - مزایا:  

     برای مدیریت حالتهای پیچیده با منطقهای شرطی مناسب است.  

     ساختار منظمتری نسبت به useState ارائه میدهد.  

     امکان جدا کردن منطق بهروزرسانی حالت (reducer) از رابط کاربری.  

   - معایب:  

     نیاز به نوشتن کد بیشتری نسبت به useState دارد.  

     برای پروژههای خیلی ساده ممکن است بیش از حد پیچیده به نظر برسد.  


3. Context API  

   - مزایا:  

     مناسب برای مدیریت حالتهای جهانی ساده که بین چند کامپوننت به اشتراک گذاشته میشوند.  

     بخشی از ریاکت است و نیازی به نصب کتابخانه اضافی ندارد.  

     استفاده آسان با هوک useContext.  

   - معایب:  

     در برنامههای بزرگ ممکن است عملکرد ضعیفی داشته باشد.  

     برای مدیریت حالتهای بسیار پیچیده مناسب نیست و نیاز به ترکیب با ابزارهای دیگر دارد.  


4. Redux  

   - مزایا:  

     ساختار قوی و قابل پیشبینی برای مدیریت حالتهای جهانی.  

     ابزارهای توسعه (مانند Redux DevTools) برای دیباگ کردن عالی هستند.  

     مناسب برای برنامههای بزرگ و پیچیده.  

   - معایب:  

     نیاز به Boilerplate زیاد (کدهای تکراری و تنظیمات اولیه).  

     پیچیدگی بیشتری نسبت به سایر ابزارها دارد.  

     یادگیری آن برای مبتدیان ممکن است زمانبر باشد.  


5. Zustand  

   - مزایا:  

     سبک، ساده و انعطافپذیر.  

     نیاز به Boilerplate کمتری نسبت به Redux دارد.  

     استفاده آسان و مناسب برای پروژههای کوچک تا متوسط.  

   - معایب:  

     ابزارهای توسعه و اکوسیستم آن به اندازه Redux قوی نیست.  

     برای پروژههای بسیار بزرگ ممکن است نیاز به تنظیمات اضافی داشته باشد.  


6. SWR/React Query  

   - مزایا:  

     بسیار مناسب برای مدیریت دادههای دریافتی از سرور (مانند APIها).  

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

     تجربه کاربری بهتری برای دادههای پویا فراهم میکنند.  

   - معایب:  

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

     نیاز به نصب کتابخانه اضافی و یادگیری مفاهیم جدید دارند.  


 5. جمعبندی

- برای پروژههای کوچک: از useState و useReducer استفاده کنید.

- برای پروژههای متوسط: Context API یا Zustand مناسب است.

- برای پروژههای بزرگ: Redux یا ابزارهای مشابه را در نظر بگیرید.

- در نکستجیاس: از SSR/SSG برای دادههای اولیه و SWR/React Query برای دادههای پویا استفاده کنید.