useCallback — مفاهیم کلیدی، هوک memoization توابع در React

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

useCallback — هوک React است که نسخه memoize شده تابع را برمی‌گرداند؛ تابعی که تا زمانی که وابستگی‌هایش تغییر نکنند، بین رندرها تغییر نمی‌کند. برخلاف تعریف معمولی تابع در داخل کامپوننت (که در هر رندر یک تابع جدید می‌سازد)، useCallback ارجاع به تابع را پایدار می‌کند و این کار از رندرهای غیرضروری کامپوننت‌های فرزندی که با React.memo بهینه شده‌اند جلوگیری می‌کند. طبق React Documentation (2025)، useCallback فقط در ترکیب با React.memo یا هوک‌هایی که به ارجاع پایدار وابسته‌اند مفید است.

نکات کلیدی

  • useCallback — هوک memoization تابع است که ارجاع را بین رندرها حفظ می‌کند.
  • React.memo — useCallback فقط در ترکیب با React.memo برای جلوگیری از رندرهای اضافی مؤثر است.
  • وابستگی‌ها — تابع فقط هنگام تغییر مقادیر مشخص‌شده در آرایه دوباره ساخته می‌شود.
  • ارجاع پایدار — useCallback تضمین می‌کند که ارجاع تابع بدون نیاز تغییر نکند.
  • زیاده‌روی نکن — استفاده بیش از حد از useCallback بدون React.memo عملکرد را بدتر می‌کند.

useCallback در React چیست

useCallback — هوکی است که در React 16.8 اضافه شده و تابع را memoize می‌کند: تا زمانی که وابستگی‌ها تغییر نکنند، همان ارجاع را برمی‌گرداند. بدون useCallback هر تعریف تابع در داخل کامپوننت، در هر رندر یک شیء تابع جدید می‌سازد. برای مقادیر اولیه این موضوع نامحسوس است، اما هنگام ارسال چنین callbackهایی به کامپوننت‌های فرزندی که با React.memo بهینه شده‌اند، هر ارجاع جدید باعث رندر مجدد کامپوننت فرزند می‌شود.

از نظر نحوی، useCallback معادل useMemo برای تابع است: useCallback(fn, deps) — مخففی برای useMemo(() => fn, deps) است. React تابع memoize شده را در حافظه داخلی گره fiber ذخیره می‌کند و در هر رندر وابستگی‌ها را مقایسه می‌کند. اگر وابستگی‌ها تغییر نکرده باشند (Object.is برای هر عنصر)، تابع قبلی برگردانده می‌شود.

طبق React Documentation — useCallback (2025)، لازم نیست هر تابع را در useCallback بپیچیم. این هوک هزینه خودش را دارد: فراخوانی هوک، مقایسه وابستگی‌ها و تخصیص حافظه برای آرایه وابستگی‌ها. اگر کامپوننت ساده است و درخت‌های عمیق با React.memo ندارد، useCallback فقط برنامه را کندتر می‌کند. بهینه‌سازی باید قابل اندازه‌گیری باشد، نه شهودی.

jsx
import { useCallback } from 'react';

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

    // ارجاع پایدار — تا تغییر deps همان تابع
    const handleClick = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

    return <Child onClick={handleClick} />;
}

memoization توابع چگونه کار می‌کند

memoization در useCallback بر اساس کش کردن نتیجه فراخوانی تابع است. React closure ساخته‌شده در لحظه رندر اول را حفظ می‌کند و در رندرهای بعدی تا زمانی که وابستگی‌ها بدون تغییر بمانند، همان را برمی‌گرداند. در داخل گره fiber، هر فراخوانی useCallback یک گره در لیست پیوندی هوک‌ها ایجاد می‌کند که وابستگی‌های قبلی و مقدار memoize شده در آن ذخیره می‌شود.

فرآیند مقایسه وابستگی‌ها دقیقاً از طریق Object.is انجام می‌شود — این مقایسه‌ای سطحی است، بدون بررسی عمیق اشیا و آرایه‌ها. اگر در وابستگی‌ها شیء یا آرایه وجود داشته باشد، ارجاع جدید در هر رندر به‌عنوان تغییر در نظر گرفته می‌شود. بنابراین در آرایه وابستگی‌ها باید مقادیر اولیه یا ارجاع‌های پایدار (مثلاً از useRef یا useMemo) ارسال شود.

طبق React Core Team — Optimization Guide (2024)، هزینه memoization شامل سه مؤلفه است: تخصیص آرایه وابستگی‌ها در هر رندر، پیمایش و مقایسه عناصر از طریق Object.is و هزینه احتمالی جمع‌آوری زباله هنگام بازآفرینی. برای کامپوننتی با صدها بسته‌بندی useCallback این می‌تواند محسوس باشد — بنابراین انتخاب‌گری در استفاده از هوک حیاتی است.

سناریوبدون useCallbackبا useCallback
ساخت تابعجدید در هر رندریکسان با deps پایدار
ارسال به React.memoفرزند رندر مجدد می‌شودفرزند رندر مجدد نمی‌شود
در آرایه useEffectافکت دوباره اجرا می‌شودافکت پایدار است
هزینهحداقلمقایسه وابستگی‌ها + حافظه

useCallback و عملکرد

این باور رایج است که useCallback به‌طور خودکار عملکرد را بهبود می‌بخشد. در واقع، به‌صورت مجزا (بدون React.memo) useCallback حتی کمی برنامه را کندتر می‌کند به دلیل هزینه مقایسه وابستگی‌ها. هوک فقط در سه سناریو فایده واقعی دارد: جلوگیری از رندر مجدد کامپوننت‌های React.memo، پایداری callbackها در useEffect و ارسال به هوک‌های سفارشی که به reference equality وابسته‌اند.

قانون ساده است: تا وقتی مشکل عملکردی را از طریق React DevTools Profiler پیدا نکرده‌ای، از useCallback استفاده نکن. تیم React بارها تأکید کرده که بهینه‌سازی زودهنگام ریشه شر است. اول کد تمیز بدون memoization بنویس، اندازه بگیر، گلوگاه را در پروفایلر پیدا کن و فقط بعداً useCallback را در جاهایی که واقعاً لازم است اضافه کن.

jsx
// بهینه‌سازی قابل اندازه‌گیری: Child در React.memo پیچیده شده
const Child = React.memo(({ onClick }) => {
    console.log('Child رندر مجدد شد');
    return <button onClick={onClick}>Click</button>;
});

function Parent() {
    const handleClick = useCallback(() => {
        console.log('کلیک شد');
    }, []);
    return <Child onClick={handleClick} />;
}

طبق Dan Abramov — Before You memo() (2024)، بیش از 90 درصد موارد استفاده از useCallback در پروژه‌های متن‌باز اضافی است. توسعه‌دهندگان هر تابع را «برای هر احتیاطی» می‌پیچند بدون اینکه اثر آن را اندازه بگیرند. جایگزین: اگر کامپوننت فرزند سنگین است و رندر مجدد آن گران است، React.memo + useCallback توجیه دارد. اگر کامپوننت فرزند سبک است، رندر مجدد ارزان‌تر از مقایسه وابستگی‌هاست.

چه زمانی از useCallback استفاده کنیم

سناریوی اول — React.memo. اگر کامپوننت فرزند در React.memo پیچیده شده و تابع callback را به‌عنوان prop می‌پذیرد، بدون useCallback کامپوننت فرزند در هر رندر والد دوباره رندر می‌شود، حتی اگر داده‌های خودش تغییر نکرده باشد. useCallback ارجاع را پایدار می‌کند و React.memo می‌تواند رندر مجدد را به‌درستی رد کند.

سناریوی دوم — useEffect با callback در وابستگی‌ها. اگر تابع به آرایه وابستگی‌های useEffect ارسال شود، هر ارجاع جدید افکت را دوباره اجرا می‌کند. useCallback تضمین می‌کند که ارجاع پایدار است و افکت فقط هنگام تغییر داده‌های واقعی اجرا می‌شود، نه در هر رندر. این برای اشتراک‌ها و درخواست‌ها بسیار مهم است.

  • React.memo children — هنگام ارسال callbackها از رندر مجدد کامپوننت‌های فرزند memoize شده جلوگیری می‌کند.
  • useEffect dependencies — تابع را در آرایه وابستگی‌های افکت پایدار می‌کند و از اجراهای اضافی جلوگیری می‌کند.
  • هوک‌های سفارشی — اگر هوک callback می‌پذیرد و به reference equality آن وابسته است، useCallback ضروری است.
  • Context value — اگر تابع در value زمینه ارسال می‌شود، useMemo همراه با useCallback ارجاع را پایدار می‌کند.
jsx
// useCallback برای وابستگی پایدار useEffect
const fetchData = useCallback(async (id) => {
    const res = await fetch(`/api/${id}`);
    setData(res.data);
}, []); // ارجاع پایدار، هرگز دوباره ساخته نمی‌شود

useEffect(() => {
    fetchData(props.id);
}, [props.id, fetchData]); // افکت فقط هنگام تغییر props.id اجرا می‌شود

useCallback در برابر useMemo

تفاوت اصلی بین useCallback و useMemo در این است که هر کدام چه چیزی را memoize می‌کنند. useCallback تابع را memoize می‌کند: useCallback(fn, deps) همان fn (نسخه یکسان یا قبلی) را برمی‌گرداند. useMemo نتیجه فراخوانی تابع را memoize می‌کند: useMemo(() => computeExpensive(a, b), [a, b]) مقدار محاسبه‌شده را برمی‌گرداند، نه تابع را.

از نظر فنی، useCallback شکر نحوی بر روی useMemo است: useCallback(fn, deps) معادل useMemo(() => fn, deps) است. این نحو فقط برای خوانایی وجود دارد — تا توسعه‌دهنده به‌وضوح ببیند که دقیقاً تابع memoize می‌شود، نه مقدار. تفاوتی در عملکرد بین useCallback و useMemo با تابع وجود ندارد — آنها کد یکسانی تولید می‌کنند.

هوکmemoize می‌کندنحوکاربرد
useCallbackتابع (ارجاع)useCallback(fn, deps)callback برای کامپوننت‌های فرزند
useMemoنتیجه محاسبهuseMemo(() => value, deps)محاسبات گران، memoization اشیا
jsx
// این‌ها معادل هستند:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);

خطاهای رایج با useCallback

رایج‌ترین خطا — پیچیدن بی‌معنای همه توابع در useCallback بدون React.memo روی کامپوننت‌های فرزند است. اگر کامپوننت فرزند در React.memo پیچیده نشده باشد، در هر رندر والد دوباره رندر می‌شود، صرف‌نظر از اینکه ارجاع callback تغییر کند یا نه. useCallback بدون React.memo — هزینه بدون منفعت است.

  • useCallback بدون React.memo — کامپوننت فرزند همچنان رندر مجدد می‌شود؛ بهینه‌سازی بی‌معنی است.
  • وابستگی‌های فراموش‌شده — اگر داخل callback متغیری استفاده شود که در deps مشخص نشده، callback شامل stale closure است.
  • memoization بیش از حد — پیچیدن هر تابع، از جمله onClickهای ساده با console.log.
  • اشیا و آرایه‌ها در وابستگی‌ها — ارجاع جدید در هر رندر به‌عنوان تغییر در نظر گرفته می‌شود، memoization بی‌فایده است.
jsx
// ❌ بی‌فایده: بدون React.memo روی child
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child بدون React.memo همچنان رندر مجدد می‌شود

// ❌ Stale closure: وابستگی جاافتاده
const handle = useCallback(() => {
    console.log(count); // count همیشه ۰ است — stale closure!
}, []);

// ✅ درست: وابستگی‌ها را ذکر کنید
const handle = useCallback(() => {
    console.log(count);
}, [count]);

مشکل stale closure در useCallback با ذکر همه متغیرهای استفاده‌شده در آرایه وابستگی‌ها حل می‌شود. eslint-plugin-react-hooks با exhaustive-deps به‌طور خودکار بررسی می‌کند که همه متغیرهای داخل بدنه callback در آرایه حضور دارند. اگر callback از setState استفاده کند که بین رندرها تغییر نمی‌کند، می‌توان با خیال راحت آن را در deps ذکر کرد — React پایداری setState را تضمین می‌کند.

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

آیا لازم است هر تابع را در useCallback بپیچیم؟

خیر. useCallback فقط در سه مورد معنا دارد: کامپوننت فرزند در React.memo پیچیده شده، تابع در آرایه وابستگی‌های useEffect استفاده می‌شود، یا تابع به هوک سفارشی وابسته به reference equality ارسال می‌شود. در سایر موارد useCallback هزینه اضافه بدون منفعت است. تیم React توصیه می‌کند ابتدا بدون بهینه‌سازی بنویسید و آن‌ها را بر اساس نتایج پروفایلینگ اضافه کنید.

کدام سریع‌تر است — useCallback یا تابع جدید در هر رندر؟

برای کامپوننت‌های ساده — تابع جدید در هر رندر کمی سریع‌تر است، زیرا useCallback منابع را صرف مقایسه وابستگی‌ها و تخصیص آرایه می‌کند. برای کامپوننت‌هایی با درخت‌های عمیق React.memo — useCallback برنده است و از رندر مجدد هزاران عنصر فرزند جلوگیری می‌کند. اندازه بگیر و مقایسه کن، حدس نزن — برای ارزیابی عینی از React DevTools Profiler استفاده کن.

آیا می‌توان از useCallback با توابع async استفاده کرد؟

بله، useCallback با توابع async دقیقاً مانند توابع همزمان کار می‌کند. هوک خود تابع را memoize می‌کند، و نتیجه (Promise) هر بار هنگام فراخوانی برگردانده می‌شود. تابع ناهمزمان داخل useCallback الگویی رایج برای callbackهای پایدار بارگذاری داده است که در useEffect استفاده می‌شوند: const fetchData = useCallback(async (id) => {...}, []).

چگونه مشکل useCallback و React.memo را اشکال‌زدایی کنیم؟

از React DevTools Profiler استفاده کن — نشان می‌دهد کدام کامپوننت‌ها رندر مجدد می‌شوند و چرا. برای بررسی برنامه‌ای console.log یا useWhyDidYouUpdate — کتابخانه‌ای که دلیل رندر مجدد را ثبت می‌کند — اضافه کن. دلایل اصلی: تغییر prop (از جمله ارجاع callback)، تغییر state یا زمینه. اگر useCallback کمکی نمی‌کند، بررسی کن که همه وابستگی‌ها به‌درستی ذکر شده‌اند.

چگونه useCallback را بدون React.memo به کامپوننت فرزند ارسال کنیم؟

حتی بدون React.memo، useCallback می‌تواند در ترکیب با useMemo برای value زمینه مفید باشد. اگر شیءای با توابع به Context.Provider ارسال می‌کنی، ساخت شیء را در useMemo بپیچ و هر تابع را در useCallback. این کار از رندر مجدد همه مصرف‌کنندگان زمینه هنگام تغییر یکی از توابع جلوگیری می‌کند. اما برای ارسال مستقیم callback در props بدون React.memo، useCallback فایده‌ای ندارد.

خلاصه

  • useCallback — هوک memoization تابع است که ارجاع پایدار را تا تغییر وابستگی‌ها برمی‌گرداند.
  • React.memo synergy — useCallback فقط در ترکیب با React.memo روی کامپوننت‌های فرزند مؤثر است.
  • مقایسه وابستگی‌ها — از طریق Object.is؛ اشیا/آرایه‌ها در deps memoization را خراب می‌کنند.
  • اندازه بگیر، حدس نزن — useCallback را فقط پس از یافتن گلوگاه با پروفایلر اضافه کن.
  • Stable closure — همه متغیرهای استفاده‌شده را در آرایه وابستگی‌ها قرار بده، وگرنه stale closure خواهی داشت.
  • useCallback در برابر useMemo — useCallback تابع را memoize می‌کند، useMemo نتیجه محاسبه را.
  • اضافه بودن — بیش از 90 درصد موارد استفاده از useCallback در پروژه‌های واقعی بهینه‌سازی زودهنگام است.

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

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

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

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