فارسی

راهنمای جامع مدیریت وضعیت در ری‌اکت برای مخاطبان جهانی. با useState، Context API، useReducer و کتابخانه‌های محبوبی مانند Redux، Zustand و TanStack Query آشنا شوید.

تسلط بر مدیریت وضعیت در ری‌اکت: راهنمای جهانی برای توسعه‌دهندگان

در دنیای توسعه فرانت‌اند، مدیریت وضعیت یکی از حیاتی‌ترین چالش‌هاست. برای توسعه‌دهندگانی که از ری‌اکت استفاده می‌کنند، این چالش از یک نگرانی ساده در سطح کامپوننت به یک تصمیم پیچیده معماری تبدیل شده است که می‌تواند مقیاس‌پذیری، عملکرد و قابلیت نگهداری یک برنامه را تعریف کند. چه یک توسعه‌دهنده تنها در سنگاپور باشید، چه بخشی از یک تیم توزیع‌شده در سراسر اروپا، یا یک بنیان‌گذار استارتاپ در برزیل، درک چشم‌انداز مدیریت وضعیت در ری‌اکت برای ساخت برنامه‌های قوی و حرفه‌ای ضروری است.

این راهنمای جامع شما را در کل طیف مدیریت وضعیت در ری‌اکت، از ابزارهای داخلی آن گرفته تا کتابخانه‌های قدرتمند خارجی، راهنمایی خواهد کرد. ما «چرایی» پشت هر رویکرد را بررسی خواهیم کرد، مثال‌های کد عملی ارائه خواهیم داد و یک چارچوب تصمیم‌گیری ارائه می‌دهیم تا به شما کمک کند ابزار مناسب برای پروژه خود را انتخاب کنید، صرف نظر از اینکه در کجای جهان هستید.

'وضعیت' (State) در ری‌اکت چیست و چرا اینقدر مهم است؟

قبل از اینکه به سراغ ابزارها برویم، بیایید یک درک روشن و جهانی از «وضعیت» ایجاد کنیم. در اصل، وضعیت هر داده‌ای است که شرایط برنامه شما را در یک نقطه زمانی خاص توصیف می‌کند. این می‌تواند هر چیزی باشد:

ری‌اکت بر این اصل بنا شده است که UI تابعی از وضعیت است (UI = f(state)). وقتی وضعیت تغییر می‌کند، ری‌اکت به طور موثر بخش‌های لازم از UI را برای بازتاب آن تغییر، دوباره رندر می‌کند. چالش زمانی به وجود می‌آید که این وضعیت باید توسط چندین کامپوننت که مستقیماً در درخت کامپوننت به هم مرتبط نیستند، به اشتراک گذاشته و اصلاح شود. اینجاست که مدیریت وضعیت به یک نگرانی معماری حیاتی تبدیل می‌شود.

پایه و اساس: وضعیت محلی با useState

سفر هر توسعه‌دهنده ری‌اکت با هوک useState آغاز می‌شود. این ساده‌ترین راه برای تعریف یک قطعه از وضعیت است که محلی برای یک کامپوننت واحد است.

به عنوان مثال، مدیریت وضعیت یک شمارنده ساده:


import React, { useState } from 'react';

function Counter() {
  // 'count' متغیر وضعیت است
  // 'setCount' تابعی برای به‌روزرسانی آن است
  const [count, setCount] = useState(0);

  return (
    

شما {count} بار کلیک کرده‌اید

); }

useState برای وضعیتی که نیازی به اشتراک‌گذاری ندارد، مانند ورودی‌های فرم، تاگل‌ها، یا هر عنصر UI که وضعیت آن بر سایر بخش‌های برنامه تأثیر نمی‌گذارد، عالی است. مشکل زمانی شروع می‌شود که شما نیاز دارید کامپوننت دیگری مقدار `count` را بداند.

رویکرد کلاسیک: بالا بردن وضعیت (Lifting State Up) و حفاری پراپ (Prop Drilling)

روش سنتی ری‌اکت برای به اشتراک گذاشتن وضعیت بین کامپوننت‌ها، «بالا بردن آن» به نزدیک‌ترین جد مشترک آنهاست. سپس وضعیت از طریق پراپ‌ها (props) به کامپوننت‌های فرزند منتقل می‌شود. این یک الگوی اساسی و مهم در ری‌اکت است.

با این حال، با رشد برنامه‌ها، این می‌تواند به مشکلی به نام «حفاری پراپ» (prop drilling) منجر شود. این زمانی است که شما مجبورید پراپ‌ها را از طریق چندین لایه از کامپوننت‌های واسطه‌ای که خودشان به داده‌ها نیازی ندارند، عبور دهید تا فقط آن را به یک کامپوننت فرزند عمیقاً تودرتو برسانید که به آن نیاز دارد. این می‌تواند خواندن، ریفکتور کردن و نگهداری کد را دشوارتر کند.

تصور کنید ترجیح تم کاربر (مثلاً 'dark' یا 'light') باید توسط یک دکمه در اعماق درخت کامپوننت قابل دسترسی باشد. ممکن است مجبور شوید آن را اینگونه پاس دهید: App -> Layout -> Page -> Header -> ThemeToggleButton. فقط `App` (جایی که وضعیت تعریف شده است) و `ThemeToggleButton` (جایی که استفاده می‌شود) به این پراپ اهمیت می‌دهند، اما `Layout`، `Page` و `Header` مجبورند به عنوان واسطه عمل کنند. این مشکلی است که راه‌حل‌های پیشرفته‌تر مدیریت وضعیت قصد حل آن را دارند.

راه‌حل‌های داخلی ری‌اکت: قدرت Context و Reducerها

تیم ری‌اکت با تشخیص چالش حفاری پراپ، Context API و هوک `useReducer` را معرفی کردند. اینها ابزارهای قدرتمند و داخلی هستند که می‌توانند تعداد قابل توجهی از سناریوهای مدیریت وضعیت را بدون افزودن وابستگی‌های خارجی مدیریت کنند.

۱. Context API: پخش سراسری وضعیت

Context API راهی برای انتقال داده‌ها از طریق درخت کامپوننت بدون نیاز به پاس دادن دستی پراپ‌ها در هر سطح فراهم می‌کند. آن را به عنوان یک مخزن داده جهانی برای بخش خاصی از برنامه خود در نظر بگیرید.

استفاده از Context شامل سه مرحله اصلی است:

  1. ایجاد Context: از `React.createContext()` برای ایجاد یک شیء context استفاده کنید.
  2. فراهم کردن Context: از کامپوننت `Context.Provider` برای پیچیدن بخشی از درخت کامپوننت خود و پاس دادن یک `value` به آن استفاده کنید. هر کامپوننتی در داخل این provider می‌تواند به این مقدار دسترسی داشته باشد.
  3. مصرف کردن Context: از هوک `useContext` در یک کامپوننت برای اشتراک در context و دریافت مقدار فعلی آن استفاده کنید.

مثال: یک تغییردهنده تم ساده با استفاده از Context


// 1. ایجاد Context (مثلاً در فایلی به نام theme-context.js)
import { createContext, useState } from 'react';

export const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
  };

  // شیء value برای همه کامپوننت‌های مصرف‌کننده در دسترس خواهد بود
  const value = { theme, toggleTheme };

  return (
    
      {children}
    
  );
}

// 2. فراهم کردن Context (مثلاً در فایل اصلی App.js)
import { ThemeProvider } from './theme-context';
import MyPage from './MyPage';

function App() {
  return (
    
      
    
  );
}

// 3. مصرف کردن Context (مثلاً در یک کامپوننت عمیقاً تودرتو)
import { useContext } from 'react';
import { ThemeContext } from './theme-context';

function ThemeToggleButton() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    
  );
}

مزایای Context API:

معایب و ملاحظات عملکرد:

۲. هوک `useReducer`: برای انتقال‌های وضعیت قابل پیش‌بینی

در حالی که `useState` برای وضعیت ساده عالی است، `useReducer` برادر قدرتمندتر آن است که برای مدیریت منطق وضعیت پیچیده‌تر طراحی شده است. این هوک به ویژه زمانی مفید است که شما وضعیتی دارید که شامل چندین زیر-مقدار است یا زمانی که وضعیت بعدی به وضعیت قبلی بستگی دارد.

با الهام از Redux، `useReducer` شامل یک تابع `reducer` و یک تابع `dispatch` است:

مثال: یک شمارنده با اکشن‌های افزایش، کاهش و بازنشانی


import React, { useReducer } from 'react';

// ۱. تعریف وضعیت اولیه
const initialState = { count: 0 };

// ۲. ایجاد تابع reducer
function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error('نوع اکشن غیرمنتظره');
  }
}

function ReducerCounter() {
  // ۳. مقداردهی اولیه useReducer
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      

تعداد: {state.count}

{/* ۴. ارسال اکشن‌ها در تعامل کاربر */} ); }

استفاده از `useReducer` منطق به‌روزرسانی وضعیت شما را در یک مکان (تابع reducer) متمرکز می‌کند، که باعث می‌شود قابل پیش‌بینی‌تر، تست‌پذیرتر و قابل نگهداری‌تر باشد، به خصوص با افزایش پیچیدگی منطق.

زوج قدرتمند: `useContext` + `useReducer`

قدرت واقعی هوک‌های داخلی ری‌اکت زمانی محقق می‌شود که شما `useContext` و `useReducer` را ترکیب کنید. این الگو به شما امکان می‌دهد یک راه‌حل مدیریت وضعیت قوی و شبیه به Redux بدون هیچ گونه وابستگی خارجی ایجاد کنید.

این الگو فوق‌العاده است زیرا خود تابع `dispatch` یک هویت پایدار دارد و بین رندرهای مجدد تغییر نمی‌کند. این بدان معناست که کامپوننت‌هایی که فقط نیاز به `dispatch` کردن اکشن‌ها دارند، هنگام تغییر مقدار وضعیت، بیهوده دوباره رندر نمی‌شوند، که یک بهینه‌سازی عملکرد داخلی را فراهم می‌کند.

مثال: مدیریت یک سبد خرید ساده


// 1. راه‌اندازی در cart-context.js
import { createContext, useReducer, useContext } from 'react';

const CartStateContext = createContext();
const CartDispatchContext = createContext();

const cartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM':
      // منطق افزودن یک آیتم
      return [...state, action.payload];
    case 'REMOVE_ITEM':
      // منطق حذف یک آیتم با id
      return state.filter(item => item.id !== action.payload.id);
    default:
      throw new Error(`اکشن ناشناخته: ${action.type}`);
  }
};

export const CartProvider = ({ children }) => {
  const [state, dispatch] = useReducer(cartReducer, []);

  return (
    
      
        {children}
      
    
  );
};

// هوک‌های سفارشی برای مصرف آسان
export const useCart = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);

// 2. استفاده در کامپوننت‌ها
// ProductComponent.js - فقط نیاز به ارسال اکشن دارد
function ProductComponent({ product }) {
  const dispatch = useCartDispatch();
  
  const handleAddToCart = () => {
    dispatch({ type: 'ADD_ITEM', payload: product });
  };

  return ;
}

// CartDisplayComponent.js - فقط نیاز به خواندن وضعیت دارد
function CartDisplayComponent() {
  const cartItems = useCart();

  return 
آیتم‌های سبد خرید: {cartItems.length}
; }

با تقسیم وضعیت و dispatch به دو context جداگانه، ما یک مزیت عملکردی به دست می‌آوریم: کامپوننت‌هایی مانند `ProductComponent` که فقط اکشن‌ها را dispatch می‌کنند، هنگام تغییر وضعیت سبد خرید، دوباره رندر نخواهند شد.

چه زمانی به سراغ کتابخانه‌های خارجی برویم

الگوی `useContext` + `useReducer` قدرتمند است، اما یک راه‌حل جادویی نیست. با مقیاس‌پذیر شدن برنامه‌ها، ممکن است با نیازهایی مواجه شوید که توسط کتابخانه‌های خارجی اختصاصی بهتر برآورده می‌شوند. شما باید یک کتابخانه خارجی را در نظر بگیرید زمانی که:

یک تور جهانی در کتابخانه‌های محبوب مدیریت وضعیت

اکوسیستم ری‌اکت پرجنب و جوش است و طیف گسترده‌ای از راه‌حل‌های مدیریت وضعیت را ارائه می‌دهد که هر کدام فلسفه و مزایا و معایب خاص خود را دارند. بیایید برخی از محبوب‌ترین انتخاب‌ها برای توسعه‌دهندگان در سراسر جهان را بررسی کنیم.

۱. Redux (و Redux Toolkit): استاندارد تثبیت‌شده

Redux سال‌هاست که کتابخانه غالب مدیریت وضعیت بوده است. این کتابخانه یک جریان داده یک‌طرفه سختگیرانه را اعمال می‌کند که تغییرات وضعیت را قابل پیش‌بینی و قابل ردیابی می‌سازد. در حالی که Redux اولیه به خاطر کد تکراری (boilerplate) خود شناخته شده بود، رویکرد مدرن با استفاده از Redux Toolkit (RTK) این فرآیند را به طور قابل توجهی ساده کرده است.

۲. Zustand: انتخاب مینیمالیستی و بدون قید و شرط

Zustand، که در زبان آلمانی به معنای «وضعیت» است، یک رویکرد مینیمالیستی و انعطاف‌پذیر ارائه می‌دهد. این کتابخانه اغلب به عنوان یک جایگزین ساده‌تر برای Redux دیده می‌شود که مزایای یک store متمرکز را بدون کد تکراری فراهم می‌کند.


// store.js
import { create } from 'zustand';

const useBearStore = create((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}));

// MyComponent.js
function BearCounter() {
  const bears = useBearStore((state) => state.bears);
  return 

{bears} خرس در این اطراف ...

; } function Controls() { const increasePopulation = useBearStore((state) => state.increasePopulation); return ; }

۳. Jotai و Recoil: رویکرد اتمی

Jotai و Recoil (از فیسبوک) مفهوم مدیریت وضعیت «اتمی» را محبوب کرده‌اند. به جای یک شیء وضعیت بزرگ و واحد، شما وضعیت خود را به قطعات کوچک و مستقل به نام «اتم» تقسیم می‌کنید.

۴. TanStack Query (قبلاً React Query): پادشاه وضعیت سرور

شاید مهم‌ترین تغییر پارادایم در سال‌های اخیر این درک باشد که بخش زیادی از آنچه ما «وضعیت» می‌نامیم، در واقع وضعیت سرور است - داده‌هایی که روی یک سرور زندگی می‌کنند و در برنامه کلاینت ما دریافت، کش و همگام‌سازی می‌شوند. TanStack Query یک مدیر وضعیت عمومی نیست؛ این یک ابزار تخصصی برای مدیریت وضعیت سرور است، و این کار را به طور استثنایی خوب انجام می‌دهد.

انتخاب درست: یک چارچوب تصمیم‌گیری

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

  1. آیا وضعیت واقعاً سراسری است، یا می‌تواند محلی باشد؟
    همیشه با useState شروع کنید. وضعیت سراسری را معرفی نکنید مگر اینکه کاملاً ضروری باشد.
  2. آیا داده‌هایی که مدیریت می‌کنید در واقع وضعیت سرور هستند؟
    اگر داده‌ها از یک API می‌آیند، از TanStack Query استفاده کنید. این ابزار کش کردن، دریافت و همگام‌سازی را برای شما مدیریت خواهد کرد. احتمالاً ۸۰٪ از «وضعیت» برنامه شما را مدیریت خواهد کرد.
  3. برای وضعیت UI باقی‌مانده، آیا فقط نیاز به جلوگیری از حفاری پراپ دارید؟
    اگر وضعیت به ندرت به‌روز می‌شود (مثلاً تم، اطلاعات کاربر، زبان)، Context API داخلی یک راه‌حل عالی و بدون وابستگی است.
  4. آیا منطق وضعیت UI شما پیچیده است و انتقال‌های قابل پیش‌بینی دارد؟
    useReducer را با Context ترکیب کنید. این به شما یک روش قدرتمند و سازمان‌یافته برای مدیریت منطق وضعیت بدون کتابخانه‌های خارجی می‌دهد.
  5. آیا با Context با مشکلات عملکردی مواجه هستید، یا وضعیت شما از قطعات مستقل زیادی تشکیل شده است؟
    یک مدیر وضعیت اتمی مانند Jotai را در نظر بگیرید. این یک API ساده با عملکرد عالی با جلوگیری از رندرهای غیرضروری ارائه می‌دهد.
  6. آیا در حال ساخت یک برنامه سازمانی در مقیاس بزرگ هستید که به یک معماری سختگیرانه و قابل پیش‌بینی، میان‌افزار و ابزارهای دیباگینگ قدرتمند نیاز دارد؟
    این مورد استفاده اصلی برای Redux Toolkit است. ساختار و اکوسیستم آن برای پیچیدگی و قابلیت نگهداری طولانی‌مدت در تیم‌های بزرگ طراحی شده است.

جدول مقایسه خلاصه

راه‌حل بهترین برای مزیت کلیدی منحنی یادگیری
useState وضعیت محلی کامپوننت ساده، داخلی بسیار کم
Context API وضعیت سراسری با فرکانس پایین (تم، احراز هویت) حل مشکل حفاری پراپ، داخلی کم
useReducer + Context وضعیت UI پیچیده بدون کتابخانه‌های خارجی منطق سازمان‌یافته، داخلی متوسط
TanStack Query وضعیت سرور (کش/همگام‌سازی داده‌های API) حذف حجم عظیمی از منطق وضعیت متوسط
Zustand / Jotai وضعیت سراسری ساده، بهینه‌سازی عملکرد کد تکراری حداقلی، عملکرد عالی کم
Redux Toolkit برنامه‌های مقیاس بزرگ با وضعیت پیچیده و مشترک قابلیت پیش‌بینی، ابزارهای توسعه قدرتمند، اکوسیستم زیاد

نتیجه‌گیری: یک دیدگاه عمل‌گرایانه و جهانی

دنیای مدیریت وضعیت در ری‌اکت دیگر نبرد یک کتابخانه در مقابل دیگری نیست. این دنیا به یک چشم‌انداز پیچیده تبدیل شده است که در آن ابزارهای مختلف برای حل مشکلات مختلف طراحی شده‌اند. رویکرد مدرن و عمل‌گرایانه، درک مزایا و معایب و ساختن یک «جعبه ابزار مدیریت وضعیت» برای برنامه شماست.

برای اکثر پروژه‌ها در سراسر جهان، یک پشته قدرتمند و موثر با این موارد شروع می‌شود:

  1. TanStack Query برای تمام وضعیت سرور.
  2. useState برای تمام وضعیت UI ساده و غیر مشترک.
  3. useContext برای وضعیت UI سراسری ساده و با فرکانس پایین.

فقط زمانی که این ابزارها کافی نباشند، باید به سراغ یک کتابخانه وضعیت سراسری اختصاصی مانند Jotai، Zustand یا Redux Toolkit بروید. با تمایز واضح بین وضعیت سرور و وضعیت کلاینت، و با شروع از ساده‌ترین راه‌حل، می‌توانید برنامه‌هایی بسازید که عملکرد بالا، مقیاس‌پذیر و لذت‌بخش برای نگهداری باشند، صرف نظر از اندازه تیم شما یا مکان کاربران شما.