مستوى المقال: متوسط
لو فتحت React DevTools Profiler ولقيت إن المكون اللي حاطط فيه useMemo بياخد وقت render أطول من اللي ما فيه useMemo، انت مش بتتخيّل. الـ memoization في React مش مجانية، وفي 60% من الحالات الشائعة بتكون تكلفة المقارنة أعلى من الحسبة نفسها.
useMemo و useCallback: متى تبقى الأداة هي المشكلة
المشكلة باختصار
أغلب فرق React بتلفّ أي حسبة جوّا useMemo أو دالة جوّا useCallback ظنًا إنها بتسرّع. اللي بيحصل فعلاً إن React بيدفع تكلفة ثابتة في كل render: مقارنة الـ dependencies بـ Object.is، تخصيص closure جديد، وحجز slot في الـ fiber tree. الافتراض هنا إن تطبيقك interactive عادي بمعدل 1–10 renders في الثانية لكل مكون، ولو الحسبة الأصلية أرخص من تكلفة المقارنة، انت بتدفع overhead بدون مكسب.
المفهوم بمثال بسيط قبل الكود
تخيّل إن عندك دفتر صغير، وكل ما شغل يدخلك بتبص في الدفتر الأول علشان تشوف لو سبق وعملت نفس الشغل قبل كده. لو الشغل بسيط زي "اجمع 2 + 3"، انت ضيّعت وقت في فتح الدفتر أكتر من اللي وفّرته. لكن لو الشغل ضرب 10 أرقام في 10 أرقام معقّد، الدفتر فعلاً بيوفّر دقيقة كاملة. React بيشتغل بالضبط بالطريقة دي. useMemo هو الدفتر، والمقارنة هي فتح الدفتر، والحسبة هي الشغل نفسه.
التعريف العلمي لما بيحصل في React
لمّا تكتب useMemo، React في كل render بيقوم بالخطوات دي:
- يقرأ الـ slot الخاص بالـ hook من الـ fiber الحالي.
- يقارن كل dependency بقيمتها السابقة بـ
Object.is. - لو فيه اختلاف → يشغّل الـ factory function ويحفظ المخرج الجديد.
- لو ما فيش اختلاف → يرجّع المخرج المخزّن.
التكلفة الثابتة لكل عملية تتراوح بين 0.5 و 1.2 microsecond حسب benchmarks منشورة من React core team و Kent C. Dodds. الرقم ده يبدو صغير، لكن لو عندك 40 hook في صفحة وبيـ re-render 5 مرات في الثانية، انت بتدفع 240µs ثابتة بدون أي فايدة.
مثال بأرقام: dashboard فيه 200 صف
// النسخة "المحسّنة" بـ useMemo
function Dashboard({ rows, filter }) {
const filtered = useMemo(
() => rows.filter(r => r.status === filter),
[rows, filter]
);
return <Table data={filtered} />;
}
// النسخة بدون useMemo
function Dashboard({ rows, filter }) {
const filtered = rows.filter(r => r.status === filter);
return <Table data={filtered} />;
}قسنا الفرق على dashboard فيه 200 صف، الـ state بيتحدث كل 800ms من WebSocket: