هوک‌های React — چیست، هوک‌های اصلی و قوانین استفاده

نویسنده: IT Sectr منتشر شده: 2026-07-04 زمان مطالعه: 10 دقیقه

هوک‌های React — توابعی هستند که امکان استفاده از state، افکت‌ها، context و سایر قابلیت‌های React را در کامپوننت‌های تابعی بدون نوشتن کلاس فراهم می‌کنند. هوک‌ها که در React 16.8 معرفی شدند، رویکرد توسعه را رادیکالاً تغییر دادند و الگوهای پیچیده‌ای مانند HOC و render props را جایگزین کردند. به گفته React, 2024، ۱۵ هوک داخلی وجود دارد که تمام سناریوهای مدیریت کامپوننت را پوشش می‌دهند.

مهمترین نکات

  • هوک‌ها — توابعی برای اتصال قابلیت‌های React به کامپوننت‌های تابعی بدون extends Component.
  • useState — هوک پایه برای مدیریت state، مقدار و setter را برمی‌گرداند.
  • useEffect — برای عوارض جانبی: درخواست‌های API، اشتراک‌ها، تایمرها، دستکاری DOM.
  • useContext — برای دسترسی به context React بدون پیچیده‌سازی Consumer و static contextType.
  • قوانین هوک‌ها — فقط در سطح بالایی و فقط داخل کامپوننت‌های تابعی یا هوک‌های سفارشی فراخوانی شوند.

هوک‌های React چیست؟

هوک‌های React — مجموعه‌ای از توابع داخلی React هستند که امکان «اتصال» به مکانیزم‌های داخلی فریم‌ورک را بدون ایجاد کلاس فراهم می‌کنند. قبل از React 16.8، کامپوننت‌های تابعی فقط می‌توانستند props بپذیرند و JSX برگردانند — هر منطق پیچیده‌ای نیاز به کامپوننت کلاسی داشت.

مشکل کامپوننت‌های کلاسی — جداسازی منطق مرتبط. کدی که به یک موجودیت تعلق داشت (مثلاً اشتراک WebSocket) به‌اجبار بین componentDidMount، componentDidUpdate و componentWillUnmount تقسیم می‌شد. هوک‌ها این را حل می‌کنند: تمام منطق در یک useEffect گروه‌بندی می‌شود.

مشکل دوم — استفاده مجدد از منطق. برای استفاده مجدد از منطق state بین کامپوننت‌های کلاسی باید از HOC (کامپوننت‌های مرتبه بالاتر) یا render props استفاده می‌شد — هر دو الگو تودرتو اضافی در درخت کامپوننت ایجاد می‌کردند. هوک‌های سفارشی این مشکل را بدون تودرتو حل می‌کنند.

useState — مدیریت state

useState — پایه‌ترین هوک. یک مقدار اولیه می‌پذیرد و یک چندتایی برمی‌گرداند: مقدار فعلی و تابعی برای به‌روزرسانی آن. برخلاف this.setState در کلاس‌ها، useState اشیاء را ادغام نمی‌کند — state جدید کاملاً جایگزین state قبلی برای فراخوانی خاص useState می‌شود.

js
import React, { useState } from 'react';

function Form() {
  const [text, setText] = useState('');
  const [submitting, setSubmitting] = useState(false);

  async function handleSubmit() {
    setSubmitting(true);
    await fetch('/api/submit', {
      method: 'POST',
      body: JSON.stringify({ text })
    });
    setSubmitting(false);
  }

  return (
    <div>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <button onClick={handleSubmit}
        disabled={submitting}>
        {submitting ? 'Saving...' : 'Submit'}
      </button>
    </div>
  );
}

فرم تابعی setState

setter useState می‌تواند تابعی دریافت کند که مقدار قبلی را گرفته و مقدار جدید را برمی‌گرداند. فرم تابعی زمانی الزامی است که state جدید از state قبلی محاسبه می‌شود و فراخوانی‌های دسته‌ای setState ممکن است — هر فراخوانی مقدار قبلی به‌روز را دریافت خواهد کرد.

useEffect — عوارض جانبی و چرخه حیات

useEffect — هوک برای انجام عوارض جانبی در کامپوننت‌های تابعی. دو آرگومان می‌پذیرد: تابع-افکت و آرایه وابستگی‌ها. React افکت را بعد از هر رندر اجرا می‌کند، اما با تعیین وابستگی‌ها — فقط وقتی هرکدام از آنها تغییر کرده است.

آرایه خالی وابستگی‌ها [] به این معناست که افکت یک بار بعد از اولین رندر اجرا می‌شود (مشابه componentDidMount). برگرداندن تابع از افکت cleanup است که هنگام unmount (مشابه componentWillUnmount) و قبل از اجرای مجدد افکت اجرا می‌شود.

js
import React, { useState, useEffect } from 'react';

function ChatRoom({ roomId }) {
  const [messages, setMessages] = useState([]);

  useEffect(() => {
    const socket = new WebSocket(`wss://chat/${roomId}`);

    socket.onmessage = (event) => {
      setMessages(prev => [...prev, JSON.parse(event.data)]);
    };

    // Cleanup - بستن سوکت
    return () => socket.close();
  }, [roomId]);

  return <ul>
    {messages.map((msg, i) =>
      <li key={i}>{msg.text}</li>
    )}
  </ul>;
}

وابستگی‌های useEffect — تنها ارتباط بین افکت و دنیای خارج هستند. اگر در افکت از متغیری از props یا state استفاده می‌شود، باید در آرایه وابستگی‌ها باشد. نادیده گرفتن این قانون منشأ باگ‌های داده‌های قدیمی (stale closure) است.

useContext — دسترسی به context

useContext — هوک برای خواندن مقدار context. شیء context ایجاد شده با React.createContext را می‌پذیرد و مقدار فعلی را برمی‌گرداند. اگر context تغییر کند، کامپوننت به‌طور خودکار با مقدار جدید دوباره رندر می‌شود.

قبل از ظهور useContext، دسترسی به context در کامپوننت‌های تابعی نیاز به پیچیده‌سازی در Context.Consumer داشت که تودرتو ایجاد می‌کرد. useContext کاملاً Consumer را حذف می‌کند — کافی است هوک را در هر جای کامپوننت تابعی فراخوانی کنید.

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

const ThemeContext = createContext('روشن');

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return <button
    className={`btn btn-${theme}`}
  >
    {theme === 'dark' ? 'moon' : '☀️'}
  </button>;
}

useContext جایگزین Redux یا state سراسری نیست. React توصیه می‌کند از context برای مقادیر کم‌تغییر استفاده کنید — تم، زبان، احراز هویت. برای داده‌هایی که مکرراً به‌روز می‌شوند، هر رندر تمام مصرف‌کنندگان context را دوباره محاسبه می‌کند که می‌تواند عملکرد را کاهش دهد.

useReducer — منطق پیچیده state

useReducer — جایگزین useState برای state‌هایی با منطق به‌روزرسانی پیچیده. تابع reducer و state اولیه را می‌پذیرد، state فعلی و تابع dispatch را برمی‌گرداند. Reducer state فعلی و action را دریافت کرده، state جدید را برمی‌گرداند — یک تابع خالص بدون عوارض جانبی.

useReducer به‌ویژه زمانی مفید است که چندین action (set, reset, increment, addItem) یک state را مدیریت می‌کنند. به جای چندین useState با setterهای مختلف — یک reducer با انواع action مشخص. این منطق را قابل پیش‌بینی و به‌راحتی قابل آزمایش می‌کند.

js
import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + 1 };
    case 'setStep':
      return { ...state, step: action.step };
    case 'reset':
      return { count: 0, step: 1 };
    default:
      throw new Error('Unknown action: ' + action.type);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    step: 1
  });

  return (
    <>
      <p>Count: {state.count}</p>
      <button
        onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button
        onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </>
  );
}

useRef, useMemo, useCallback — بهینه‌سازی

useRef یک شیء قابل تغییر ایجاد می‌کند که بین رندرها حفظ می‌شود. تغییر current باعث رندر مجدد نمی‌شود. کاربرد اصلی — دسترسی به عناصر DOM و ذخیره مقادیر قابل تغییر (تایمرها، مقادیر قبلی، نمونه‌های کتابخانه‌ها).

useMemo نتیجه تابع را حافظه‌گذاری می‌کند. مقدار را فقط وقتی یکی از وابستگی‌ها تغییر کند دوباره محاسبه می‌کند. برای محاسبات سنگین استفاده کنید — فیلتر کردن آرایه‌های بزرگ، مرتب‌سازی، قالب‌بندی داده‌ها. useCallback — حالت خاص useMemo برای حافظه‌گذاری توابع.

js
import React, { useRef, useMemo, useCallback } from 'react';

function SearchPage({ items, query, onItemSelect }) {
  const inputRef = useRef(null);

  // فوکوس خودکار هنگام mount
  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  // ذخیره نتیجه فیلتر شده
  const filtered = useMemo(() => {
    return items.filter(item =>
      item.name.toLowerCase()
        .includes(query.toLowerCase())
    );
  }, [items, query]);

  // ذخیره callback
  const handleSelect = useCallback((id) => {
    onItemSelect(id);
  }, [onItemSelect]);

  return (
    <div>
      <input ref={inputRef}
        value={query}
        onChange={e => setQuery(e.target.value)}
      />
      <ul>
        {filtered.map(item =>
          <li key={item.id}>
            <button
              onClick={() => handleSelect(item.id)}>
              {item.name}
            </button>
          </li>
        )}
      </ul>
    </div>
  );
}

قانون مهم: useRef, useMemo و useCallback — ابزارهای بهینه‌سازی هستند، نه ساختارهای اجباری. همه چیز را در useMemo نپیچید — ابتدا عملکرد را اندازه‌گیری کنید. بهینه‌سازی زودهنگام کد را پیچیده می‌کند. React کامپوننت‌ها را سریع رندر می‌کند؛ useMemo فقط زمانی نیاز است که محاسبات واقعاً سنگین باشند (O(n²) و بالاتر).

قوانین هوک‌ها و خطاهای رایج

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

رایج‌ترین خطا — نقض ترتیب فراخوانی هوک‌ها. React به ترتیب فراخوانی هوک‌ها بین رندرها متکی است. اگر هوک داخل شرط باشد و در یکی از رندرها فراخوانی نشود، تمام هوک‌های بعدی جابه‌جا می‌شوند و باعث باگ می‌شوند.

خطاچه اتفاقی می‌افتدراه‌حل
هوک در شرطترتیب هوک‌ها بهم می‌خوردمنطق شرط را داخل هوک منتقل کنید
Stale closureافکت از مقدار قدیمی استفاده می‌کندهمه وابستگی‌ها را به آرایه اضافه کنید
حلقه بی‌نهایتuseEffect بدون وابستگی state را تغییر می‌دهدوابستگی‌ها را مشخص کنید یا از useReducer استفاده کنید
تغییر ref در رندرعوارض جانبی در بدنه تابعجهش‌ها را به useEffect منتقل کنید

دومین خطای رایج — stale closure در useEffect. اگر در افکت از متغیری استفاده شود که در وابستگی‌ها مشخص نشده، افکت مقدار آن را در لحظه ایجاد بستار «گرفته» است. راه‌حل — همیشه تمام متغیرهای استفاده‌شده را در آرایه وابستگی‌ها قرار دهید. اگر وابستگی‌ها زیاد هستند، افکت را به چند بخش تقسیم کنید.

سوالات متداول

چرا هوک‌ها را نمی‌توان در شرط‌ها فراخوانی کرد؟

React به ترتیب فراخوانی هوک‌ها بین رندرها متکی است. اگر هوک در شرط فراخوانی شده و در یکی از رندرها کار نکرده باشد، تمام هوک‌های بعدی «جابه‌جا می‌شوند» — state آنها بهم می‌ریزد. قانون ESLint hooks/exhaustive-deps در این باره هشدار می‌دهد.

تفاوت بین useEffect و useLayoutEffect چیست؟

useLayoutEffect همگام است و قبل از اینکه مرورگر تغییرات را ترسیم کند اجرا می‌شود. useEffect ناهمگام است — بعد از ترسیم. از useLayoutEffect فقط زمانی استفاده کنید که باید DOM را قبل از دیدن تغییرات توسط کاربر اندازه‌گیری یا تغییر دهید (موقعیت popover، بازیابی اسکرول).

چگونه درخواست fetch را هنگام unmount لغو کنیم؟

داخل useEffect از AbortController استفاده کنید. یک controller ایجاد کنید، signal را به fetch بدهید و در تابع cleanup controller.abort() را فراخوانی کنید. برای مرورگرهای قدیمی پرچم cancelled کافی است: return () => { cancelled = true; } با بررسی قبل از setState.

هوک سفارشی چیست و چه زمانی ایجاد کنیم؟

هوک سفارشی — تابع JavaScript با پیشوند use که از هوک‌های داخلی استفاده می‌کند. زمانی ایجاد کنید که همان منطق با هوک‌ها در دو یا چند کامپوننت تکرار می‌شود. مثال‌ها: useDebounce، useLocalStorage، useMediaQuery. هوک‌های سفارشی جایگزین HOC و render props می‌شوند.

چرا useEffect در Strict Mode دو بار اجرا می‌شود؟

در Strict Mode React عمداً کامپوننت را در حالت توسعه دو بار mount و unmount می‌کند تا خطاهای مربوط به افکت‌های پاک‌نشده را پیدا کند. اگر cleanup به درستی نوشته شده باشد، فراخوانی دوگانه مشکلی ایجاد نمی‌کند. در حالت production این اتفاق نمی‌افتد.

خلاصه

  • هوک‌های React — توابعی برای استفاده از state، افکت‌ها و context در کامپوننت‌های تابعی که جایگزین کلاس‌ها شدند.
  • useState — هوک پایه برای state محلی با setter تابعی برای به‌روزرسانی‌های امن.
  • useEffect — هوک جهانی برای عوارض جانبی با آرایه وابستگی‌ها و تابع cleanup.
  • useContext — دسترسی مستقیم به context React بدون پیچیده‌سازی Consumer و تودرتوی اضافی.
  • useReducer — برای منطق پیچیده state با انواع action، جایگزین Redux در یک کامپوننت.
  • useRef, useMemo, useCallback — ابزارهای بهینه‌سازی: ارجاع به DOM، حافظه‌گذاری مقادیر و callbackها.
  • قانون سطح بالایی — محدودیت سختگیرانه فراخوانی هوک‌ها خارج از شرط‌ها، حلقه‌ها و توابع تودرتو.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید