useCallback — المفاهيم الأساسية، خطاف تخزين الدوال مؤقتًا في React

المؤلف: IT Sectr نُشر: 2026-07-04 وقت القراءة: 9 دق

useCallback هو خطاف React يعيد نسخة مخزنة مؤقتًا من دالة لا تتغير بين عمليات التصيير حتى تتغير تبعياتها. على عكس الإعلان العادي عن دالة داخل المكون (الذي ينشئ دالة جديدة في كل تصيير)، يعمل useCallback على تثبيت المرجع للدالة، مما يمنع إعادة التصيير غير الضرورية للمكونات الفرعية المحسّنة عبر React.memo. وفقًا React Documentation (2025)، فإن useCallback مفيد فقط مع React.memo أو الخطافات التي تعتمد على مرجع ثابت.

الخلاصة

  • useCallback — خطاف لتخزين الدالة مؤقتًا، مع الحفاظ على المرجع بين عمليات التصيير.
  • React.memo — useCallback فعال فقط مع React.memo لمنع إعادة التصيير.
  • التبعيات — يتم إعادة إنشاء الدالة فقط عند تغيير القيم المحددة في المصفوفة.
  • مرجع ثابت — يضمن useCallback أن مرجع الدالة لن يتغير بدون داعٍ.
  • لا تفرط في الاستخدام — الاستخدام المفرط لـ useCallback بدون React.memo يضر بالأداء.

ما هو useCallback في React

useCallback هو خطاف أضيف في React 16.8 يخزن الدالة مؤقتًا: يعيد نفس المرجع حتى تتغير التبعيات. بدون useCallback، كل إعلان دالة داخل المكون ينشئ كائن دالة جديد في كل تصيير. بالنسبة للقيم البدائية هذا غير ملحوظ، ولكن عند تمرير هذه الاستدعاءات إلى مكونات فرعية محسّنة بـ React.memo، كل مرجع جديد يسبب إعادة تصيير للمكون الفرعي.

من الناحية التركيبية، useCallback مكافئ لـ useMemo للدوال: useCallback(fn, deps) هو اختصار لـ useMemo(() => fn, deps). يخزن React الدالة المخزنة مؤقتًا في التخزين الداخلي لعقدة fiber ويقارن التبعيات في كل تصيير. إذا لم تتغير التبعيات (Object.is لكل عنصر)، يتم إرجاع الدالة السابقة.

وفقًا React Documentation — useCallback (2025)، لا يجب أن تلف كل دالة في useCallback. للخطاف تكلفته: استدعاء الخطاف، مقارنة التبعيات، وتخصيص ذاكرة لمصفوفة التبعيات. إذا كان المكون بسيطًا وليس لديه أشجار عميقة مع React.memo، فإن useCallback سيبطئ التطبيق فقط. يجب أن يكون التحسين قابلًا للقياس، وليس حدسيًا.

jsx
import { useCallback } from 'react';

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

    // مرجع ثابت — نفس الدالة حتى تتغير التبعيات
    const handleClick = useCallback(() => {
        setCount(prev => prev + 1);
    }, []);

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

كيف يعمل التخزين المؤقت للدوال

التخزين المؤقت في useCallback يعتمد على تخزين نتيجة استدعاء الدالة. يحتفظ React بالإغلاق (closure) الذي تم إنشاؤه في أول تصيير ويعيده في التصييرات اللاحقة طالما بقيت التبعيات دون تغيير. داخل عقدة fiber، كل استدعاء لـ useCallback ينشئ عقدة في القائمة المرتبطة للخطافات، حيث يتم تخزين التبعيات السابقة والقيمة المخزنة مؤقتًا.

مقارنة التبعيات تتم بدقة عبر Object.is — مقارنة سطحية بدون فحص عميق للكائنات أو المصفوفات. إذا كانت التبعية كائنًا أو مصفوفة، فسيتم اعتبار المرجع الجديد في كل تصيير تغييرًا. لذلك، يجب أن تحتوي مصفوفة التبعيات على قيم بدائية أو مراجع ثابتة (مثلًا من useRef أو useMemo).

وفقًا React Core Team — Optimization Guide (2024)، تشمل تكلفة التخزين المؤقت ثلاثة مكونات: تخصيص مصفوفة التبعيات في كل تصيير، تكرار ومقارنة العناصر عبر Object.is، والعبء المحتمل لجمع القمامة عند إعادة الإنشاء. بالنسبة لمكون يحتوي على مئات من أغلفة useCallback، يمكن أن يصبح هذا ملحوظًا — لذلك الانتقائية في استخدام الخطاف أمر بالغ الأهمية.

السيناريوبدون useCallbackمع useCallback
إنشاء الدالةجديدة في كل تصييرنفسها مع deps ثابتة
تمرير إلى React.memoالابن يعيد التصييرالابن لا يعيد التصيير
في مصفوفة useEffectالتأثير يعاد تشغيلهالتأثير مستقر
العبء الإضافيأدنىمقارنة التبعيات + ذاكرة

useCallback والأداء

هناك اعتقاد خاطئ واسع الانتشار أن useCallback يحسن الأداء تلقائيًا. في الواقع، بشكل منعزل (بدون React.memo)، فإن useCallback يبطئ التطبيق قليلاً بسبب تكلفة مقارنة التبعيات. يحقق الخطاف فائدة حقيقية فقط في ثلاثة سيناريوهات: منع إعادة تصيير مكونات React.memo، تثبيت الاستدعاءات في useEffect، وتمرير الاستدعاءات إلى خطافات مخصصة تعتمد على مساواة المرجع.

القاعدة بسيطة: حتى تكتشف مشكلة أداء عبر React DevTools Profiler — لا تستخدم useCallback. أكد فريق React مرارًا أن التحسين المبكر هو أصل كل الشرور. اكتب أولاً كودًا نظيفًا بدون تخزين مؤقت، قم بالقياس، ابحث عن عنق الزجاجة في الملف الشخصي، وبعدها فقط أضف 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 ويستقبل دالة استدعاء كخاصية، بدون useCallback سيعيد المكون الفرعي التصيير في كل تصيير للأب، حتى لو لم تتغير بياناته الخاصة. useCallback يثبت المرجع، مما يسمح لـ React.memo بتخطي إعادة التصيير بشكل صحيح.

السيناريو الثاني — useEffect مع استدعاء في التبعيات. إذا تم تمرير دالة إلى مصفوفة تبعيات useEffect، كل مرجع جديد سيعيد تشغيل التأثير. useCallback يضمن أن المرجع مستقر، وأن التأثير يُنفذ فقط عندما تتغير البيانات الفعلية، وليس في كل تصيير. هذا مهم بشكل خاص للاشتراكات والطلبات.

  • أبناء React.memo — يمنع إعادة تصيير المكونات الفرعية المخزنة عند تمرير الاستدعاءات.
  • تبعيات useEffect — يثبت الدالة في مصفوفة تبعيات التأثير، مما يمنع إعادة التشغيل غير الضرورية.
  • الخطافات المخصصة — إذا كان الخطاف يقبل استدعاءً ويعتمد على مساواة المرجع، فإن useCallback إلزامي.
  • قيمة السياق — إذا تم تمرير دالة في قيمة السياق، فإن 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 هو ما يخزنه كل منهما مؤقتًا. useCallback يخزن الدالة مؤقتًا: useCallback(fn, deps) يعيد fn (نفسها أو النسخة السابقة). useMemo يخزن نتيجة استدعاء دالة: useMemo(() => computeExpensive(a, b), [a, b]) يعيد القيمة المحسوبة، وليس دالة.

من الناحية الفنية، useCallback هو سكر نحوي فوق useMemo: useCallback(fn, deps) مكافئ لـ useMemo(() => fn, deps). هذه الصيغة موجودة فقط للقراءة — ليرى المطور بوضوح أنه يتم تخزين دالة وليس قيمة. لا يوجد فرق في الأداء بين useCallback و useMemo مع دالة — كلاهما يولد كودًا متطابقًا.

الخطافيخزن مؤقتًاالصيغةالاستخدام
useCallbackالدالة (المرجع)useCallback(fn, deps)استدعاءات للمكونات الفرعية
useMemoنتيجة الحسابuseMemo(() => value, deps)حسابات مكلفة، تخزين الكائنات مؤقتًا
jsx
// هذه متكافئة:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);

الأخطاء الشائعة مع useCallback

الخطأ الأكثر شيوعًا هو اللف غير المجدي لجميع الدوال في useCallback بدون React.memo على المكونات الفرعية. إذا كان المكون الفرعي غير مغلف بـ React.memo، فسيظل يعيد التصيير في كل تصيير للأب، بغض النظر عما إذا كان مرجع الاستدعاء يتغير أم لا. useCallback بدون React.memo هو تكلفة بدون فائدة.

  • useCallback بدون React.memo — المكون الفرعي لا يزال يعيد التصيير; التحسين لا معنى له.
  • تبعيات منسية — إذا كان هناك متغير مستخدم داخل الاستدعاء غير موجود في deps، فإن الاستدعاء يحتوي على إغلاق قديم (stale closure).
  • تخزين مفرط — لف كل دالة، بما في ذلك onClick التافهة مع console.log.
  • كائنات ومصفوفات في التبعيات — مرجع جديد في كل تصيير يعتبر تغييرًا، التخزين المؤقت عديم الفائدة.
jsx
// ❌ غير مفيد: لا يوجد React.memo على الابن
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child لا يزال يعيد التصيير بدون React.memo

// ❌ إغلاق قديم: تبعية مفقودة
const handle = useCallback(() => {
    console.log(count); // count دائمًا 0 — إغلاق قديم!
}, []);

// ✅ صحيح: تضمين التبعيات
const handle = useCallback(() => {
    console.log(count);
}, [count]);

يتم حل مشكلة الإغلاق القديم (stale closure) في useCallback بتضمين جميع المتغيرات المستخدمة في مصفوفة التبعيات. eslint-plugin-react-hooks مع exhaustive-deps يتحقق تلقائيًا من أن جميع المتغيرات من جسم الاستدعاء موجودة في المصفوفة. إذا كان الاستدعاء يستخدم setState، الذي لا يتغير بين التصييرات، يمكن تضمينه بأمان في deps — يضمن React استقرار setState.

الأسئلة الشائعة

هل يجب لف كل دالة في useCallback؟

لا. useCallback له معنى فقط في ثلاث حالات: المكون الفرعي مغلف بـ React.memo، الدالة مستخدمة في مصفوفة تبعيات useEffect، أو الدالة تمرر إلى خطاف مخصص يعتمد على مساواة المرجع. في الحالات الأخرى، يضيف useCallback عبئًا بدون فائدة. يوصي فريق React بالكتابة أولاً بدون تحسينات وإضافتها بناءً على نتائج التنميط.

ما الأسرع — useCallback أم دالة جديدة في كل تصيير؟

للمكونات البسيطة — دالة جديدة في كل تصيير أسرع قليلاً، لأن useCallback يستهلك موارد في مقارنة التبعيات وتخصيص المصفوفة. للمكونات ذات الأشجار العميقة من React.memo — يفوز useCallback بمنع إعادة تصيير آلاف العناصر الفرعية. قس وقارن بدلاً من التخمين — استخدم React DevTools Profiler للتقييم الموضوعي.

هل يمكن استخدام useCallback مع الدوال غير المتزامنة؟

نعم، useCallback يعمل مع الدوال غير المتزامنة تمامًا كما مع الدوال المتزامنة. يخزن الخطاف الدالة نفسها، ويتم إرجاع النتيجة (Promise) في كل مرة يتم استدعاؤها. الدالة غير المتزامنة داخل useCallback هي نمط شائع لاستدعاءات تحميل البيانات المستقرة المستخدمة في useEffect: const fetchData = useCallback(async (id) => {...}, []).

كيف تكتشف مشكلة مع useCallback و React.memo؟

استخدم React DevTools Profiler — يظهر أي المكونات تعيد التصيير ولماذا. للتحقق برمجيًا، أضف console.log أو استخدم useWhyDidYouUpdate — مكتبة تسجل سبب إعادة التصيير. الأسباب الرئيسية: تغيرت خاصية (بما في ذلك مرجع الاستدعاء)، تغيرت الحالة أو السياق. إذا لم يساعد useCallback — تحقق من أن جميع التبعيات محددة بشكل صحيح.

كيف تمرر useCallback إلى مكون فرعي بدون React.memo؟

حتى بدون React.memo، useCallback يمكن أن يكون مفيدًا مع useMemo لقيمة السياق. إذا كنت تمرر كائنًا بدوال إلى Context.Provider، لف إنشاء الكائن في useMemo، وكل دالة في useCallback. هذا يمنع إعادة تصيير جميع مستهلكي السياق عند تغيير إحدى الدوال. ولكن لتمرير الاستدعاءات مباشرة في الخصائص بدون React.memo، لا فائدة من useCallback.

الملخص

  • useCallback — خطاف لتخزين دالة مؤقتًا، يعيد مرجعًا ثابتًا حتى تتغير التبعيات.
  • التآزر مع React.memo — useCallback فعال فقط مع React.memo على المكونات الفرعية.
  • مقارنة التبعيات — عبر Object.is; الكائنات/المصفوفات في deps تكسر التخزين المؤقت.
  • قس، لا تخمن — أضف useCallback فقط بعد اكتشاف عنق الزجاجة عبر الملف الشخصي.
  • إغلاق مستقر — تضمين جميع المتغيرات المستخدمة في مصفوفة التبعيات، وإلا — إغلاق قديم.
  • useCallback ضد useMemo — useCallback يخزن دالة، useMemo يخزن نتيجة حساب.
  • الإفراط — أكثر من 90% من حالات استخدام useCallback في المشاريع الحقيقية هي تحسين مبكر.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا