اولین پروژه React جدی که روی آن کار کردم، پر از کلاس‌های سنگین Component بود. state در سازنده تعریف می‌شد، منطق چرخه حیات در پنج متد مختلف پخش بود، و اشتراک منطق بین دو کامپوننت تقریباً غیرممکن بود. بعد از مهاجرت به Hooks، همان کد به نصف حجم رسید و منطق قابل استفاده مجدد شد. از آن روز، در هیچ پروژه‌ای به الگوی کلاس برنگشتم. این نوشته درباره همان تجربه است.

React Hooks چیست و چه مشکلی را حل کرد؟

React Hooks (هوک‌های ری‌اکت) توابعی هستند که در نسخه 16.8 ری‌اکت معرفی شدند تا به کامپوننت‌های تابعی (Function Components) امکان استفاده از state و قابلیت‌های چرخه حیات را بدهند. طبق تعریف ویکی‌پدیای فارسی درباره ری‌اکت، React (ری‌اکت) یک کتابخانه جاوااسکریپتی برای ساخت رابط‌های کاربری است. قبل از Hooks، این قابلیت‌ها فقط در کامپوننت‌های کلاسی (Class Components) قابل استفاده بودند.

Hooks سه مشکل اصلی را حل کردند: دشواری اشتراک منطق بین کامپوننت‌ها (که با Higher-Order Components و Render Props سعی در حلشان داشتند ولی ایجاد پیچیدگی می‌کردند)، پخش شدن منطق یک کامپوننت بین چند متد چرخه حیات، و سختی کار با this در کلاس‌ها. برای مطالعه درباره مبانی React، React از صفر و فرانت‌اند چیست را ببینید.

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

قواعد طلایی هوک‌ها

قبل از هر چیز، دو قاعده‌ای که رعایت‌شان اجباری است:

  1. فقط در بالاترین سطح: هوک‌ها نباید داخل حلقه، شرط یا تابع تودرتو استفاده شوند. ترتیب فراخوانی هوک‌ها در هر رندر باید ثابت باشد.
  2. فقط در کامپوننت‌های React یا هوک‌های سفارشی: هوک‌ها در توابع جاوااسکریپت معمولی کار نمی‌کنند.

نقض این دو قاعده، به خطاهای مرموزی منجر می‌شود که پیدا کردنشان سخت است. ابزار eslint-plugin-react-hooks این قواعد را در حین کدنویسی گوشزد می‌کند و در همه پروژه‌های واقعی من فعال است.

useState و مدیریت state ساده

useState ساده‌ترین و پرکاربردترین هوک است. این هوک یک مقدار state و یک تابع برای به‌روزرسانی آن برمی‌گرداند:

const [count, setCount] = useState(0);
const increment = () => setCount(prev => prev + 1);

نکته مهم که در پروژه‌های واقعی خیلی دیدم: وقتی مقدار جدید state به مقدار قبلی وابسته است، همیشه از فرم تابعی setCount(prev => ...) استفاده کنید. فرم مستقیم setCount(count + 1) در به‌روزرسانی‌های سریع متوالی، ممکن است به از دست رفتن به‌روزرسانی منجر شود. برای study بیشتر درباره جاوااسکریپت مدرن، آموزش ES6 در جاوااسکریپت و آموزش جاوااسکریپت از صفر را ببینید.

useEffect و مدیریت عوارض جانبی

useEffect پرکاربردترین و در عین حال پرمخاطره‌ترین هوک است. این هوک برای اجرای عوارض جانبی (Side Effects) مثل درخواست شبکه، اشتراک به Event یا تغییر DOM استفاده می‌شود. ساختار:

useEffect(() => {
  const controller = new AbortController();
  fetch('/api/data', { signal: controller.signal })
    .then(res => res.json())
    .then(setData);
  return () => controller.abort();
}, [dependency]);

سه نکته حیاتی در استفاده از useEffect: اول، آرایه وابستگی‌ها (Dependency Array) همیشه باید کامل باشد، وگرنه اثر بی‌نهایت تکرار می‌شود. دوم، تابع پاک‌سازی (Cleanup Function) را جدی بگیرید تا اشتراک‌ها پاک شوند. سوم، از useEffect برای محاسباتی که در رندر قابل انجام هستند، پرهیز کنید. برای مطالعه بیشتر درباره مدیریت خطا، مدیریت خطا در جاوااسکریپت و Async و Await در جاوااسکریپت را ببینید.

در useEffect، وابستگی‌های ناقص، رایج‌ترین علت باگ‌هایی است که سخت پیدا می‌شوند.

useContext و اشتراک state بین کامپوننت‌ها

useContext اجازه می‌دهد داده‌ای را بدون Prop Drilling (انتقال Prop از چندین سطح میانی) بین کامپوننت‌ها به اشتراک بگذارید. الگوی متداول در پروژه‌های واقعی: Context برای تم، زبان، احراز هویت و تنظیمات کلی.

const ThemeContext = createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button className={theme}>کلیک</button>;
}

یک هشدار از تجربه: Context را برای هر چیزی استفاده نکنید. برای state های پیچیده و پرتغییر، ابزارهای مدیریت state مثل Redux یا Zustand انتخاب بهتری هستند. برای مطالعه بیشتر درباره معماری فرانت‌اند، شی‌گرایی در جاوااسکریپت و بهینه‌سازی جاوااسکریپت را ببینید.

useReducer برای state پیچیده

وقتی state پیچیده‌تر می‌شود و به‌روزرسانی‌ها وابستگی‌های زیادی دارند، useReducer انتخاب درستی است. این هوک در واقع الگوی reducer در Redux را با React ترکیب می‌کند:

const initialState = { count: 0, status: 'idle' };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'reset':
      return initialState;
    default:
      return state;
  }
}

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

در تجربه من، قاعده انتخاب بین useState و useReducer ساده است: اگر state بیش از سه فیلد دارد یا به‌روزرسانی‌ها با هم ارتباط دارند، سراغ useReducer بروید. برای مطالعه بیشتر درباره الگوهای معماری، مبانی REST و API چیست را ببینید.

useMemo و useCallback برای بهینه‌سازی

useMemo و useCallback هوک‌های بهینه‌سازی هستند. useMemo برای ذخیره نتیجه یک محاسبه سنگین استفاده می‌شود و useCallback برای ذخیره یک تابع:

const expensiveValue = useMemo(() => computeExpensive(data), [data]);

const handleClick = useCallback(() => {
  doSomething(id);
}, [id]);

هشدار مهم از تجربه پروژه‌های واقعی: از useMemo و useCallback بیش از حد استفاده نکنید. این هوک‌ها خودشان هزینه دارند و در محاسبات سبک، عملکرد را بدتر می‌کنند. فقط برای مقادیر سنگین یا برای پایدار نگه‌داشتن مرجع‌هایی که به کامپوننت فرزند پاس می‌شوند، استفاده‌شان کنید. برای مطالعه بیشتر درباره عملکرد، بهینه‌سازی عملکرد جاوااسکریپت، Core Web Vitals چیست، و چگونه سرعت فرانت‌اند را افزایش دهیم را ببینید.

useRef و دسترسی به عناصر DOM

useRef برای نگه‌داشتن یک مقدار پایدار بین رندرها استفاده می‌شود، بدون اینکه تغییرش باعث رندر مجدد شود. کاربردهای اصلی: دسترسی به عناصر DOM، ذخیره تایمرها و نگه‌داشتن مقادیر قبلی:

const inputRef = useRef(null);

useEffect(() => {
  inputRef.current?.focus();
}, []);

return <input ref={inputRef} />;

در تجربه من، useRef یکی از کم‌استفاده‌شده‌ترین هوک‌هایی است که در جای درست، حیاتی می‌شود. برای مطالعه بیشتر درباره تعامل DOM، کار با DOM در جاوااسکریپت و ایونت‌ها در جاوااسکریپت را ببینید.

ساخت هوک سفارشی

یکی از قوی‌ترین ویژگی‌های Hooks، امکان ساخت هوک‌های سفارشی است. هر تابعی که با کلمه use شروع شود و از هوک‌های دیگر استفاده کند، یک هوک سفارشی است:

function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const stored = localStorage.getItem(key);
    return stored ? JSON.parse(stored) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

هوک‌های سفارشی، همان چیزی است که Hooks را از ابزار صرفاً state به ابزار اشتراک منطق تبدیل می‌کند. در پروژه‌های واقعی، هوک‌هایی مثل useAuth، useFetch، useDebounce و useForm باعث می‌شوند منطق پیچیده در همه کامپوننت‌ها تکرار نشود.

اشتباهات رایج در استفاده از هوک‌ها

  • نقض قواعد هوک‌ها: استفاده در شرط یا حلقه، رایج‌ترین اشتباه و مرموزترین خطا.
  • آرایه وابستگی ناقص در useEffect: منبع بی‌نهایت باگ و حلقه بی‌پایان.
  • استفاده بی‌رویه از useMemo و useCallback: بهینه‌سازی زودهنگام، بیشتر ضرر می‌زند تا فایده.
  • نبود cleanup در useEffect: اشتراک‌ها و تایمرها باقی می‌مانند و به نشتی حافظه (Memory Leak) منجر می‌شوند.
  • استفاده از Context برای state پرتغییر: باعث رندر بی‌مورد کل درخت کامپوننت می‌شود.
  • استفاده از useState برای state پیچیده: کد را پیچیده و باگ‌دار می‌کند؛ useReducer انتخاب بهتری است.
  • نادیده گرفتن Stale Closure: تابعی که به مقدار قدیمی state دسترسی دارد و به‌روزرسانی نمی‌شود.

پرسش‌های پرتکرار درباره React Hooks

آیا Hooks جایگزین کامل کامپوننت‌های کلاسی شده است؟

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

آیا Hooks جایگزین Redux می‌شود؟

خیر. Hooks ابزار مدیریت state محلی است. Redux برای state سراسری پیچیده در اپلیکیشن‌های بزرگ استفاده می‌شود. برای مطالعه بیشتر درباره معماری، معماری نرم‌افزار چیست و مدیریت state در فرانت‌اند مدرن را ببینید.

چطور از useEffect برای درخواست API استفاده کنم بدون حلقه بی‌پایان؟

سه راه: آرایه وابستگی دقیق، استفاده از AbortController برای لغو درخواست، و استفاده از ابزارهایی مثل React Query یا SWR که مسائل را پنهان می‌کنند. برای مطالعه بیشتر درباره API، API چیست و اصول طراحی REST API را ببینید.

آیا هوک‌ها روی عملکرد اپلیکیشن اثر منفی دارند؟

خیر، در واقع هوک‌ها معمولاً عملکرد را بهتر می‌کنند چون کد جمع‌وجورتر و قابل بهینه‌سازی است. فقط استفاده بی‌رویه از useMemo و useCallback می‌تواند هزینه‌زا باشد.

آیا می‌توان هوک‌ها را در تست‌ها استفاده کرد؟

بله. ابزار React Testing Library و ابزار renderHook اجازه تست هوک‌ها را می‌دهند. برای مطالعه بیشتر، هوش مصنوعی چگونه باگ‌ها را پیدا می‌کند و دیباگ کردن کدهای سفارشی را ببینید.

هوک‌ها به‌عنوان یک مدل ذهنی

React Hooks، بیش از یک API جدید، یک مدل ذهنی جدید است. مدلی که در آن منطق، نه به چرخه حیات کامپوننت، بلکه به رفتار کاربر و داده‌ها گره می‌خورد. این تغییر ساده، باعث شده کامپوننت‌ها جمع‌وجورتر، منطق قابل استفاده مجددتر و باگ‌ها قابل تشخیص‌تر شوند. سه قاعده ساده: قواعد دوگانه را همیشه رعایت کنید، آرایه وابستگی در useEffect را جدی بگیرید، و هوک‌های سفارشی را برای منطق تکراری بسازید. اگر تجربه‌ای از پروژه‌ای که با مهاجرت به Hooks متحول شد یا از پروژه‌ای که در استفاده از useEffect به دام افتاد دارید، در دیدگاه‌ها بنویسید؛ همان تجربه‌های واقعی برای خواننده بعدی از هر مقاله آموزشی کاربردی‌تر است.