عندما يعاد رسم (Re-render) المكون في React، يتم تنفيذ كل سطر برلمجي بداخله من جديد:
في التطبيقات الضخمة، قد يؤدي ذلك إلى إبطاء الواجهة وتأخير استجابتها. لتفادي هذه المشكلة، تعتمد ريأكت على تقنية Memoization (التخزين المؤقت) لمنع تكرار العمليات دون داعٍ.
يُستخدم useMemo لمنع إعادة تشغيل المعالجات الحسابية الثقيلة طالما أن مدخلاتها لم تتغير.
| 1 | import { useMemo } from 'react'; |
| 2 | |
| 3 | function CourseList({ courses, filterTerm }) { |
| 4 | // ⚠️ عدم استخدام useMemo يعيد الفلترة المجهدة مع كل رندرة للمكون |
| 5 | // ✅ تخزين النتيجة: لن تُنفذ الفلترة إلا عند تغير [courses, filterTerm] |
| 6 | const filteredCourses = useMemo(() => { |
| 7 | return courses.filter((c) => c.title.includes(filterTerm)); |
| 8 | }, [courses, filterTerm]); |
| 9 | |
| 10 | return ( |
| 11 | <ul> |
| 12 | {filteredCourses.map((c) => ( |
| 13 | <li key={c.id}>{c.title}</li> |
| 14 | ))} |
| 15 | </ul> |
| 16 | ); |
| 17 | } |
في جافا سكريبت، () => {} === () => {} تُرجع false لأن مراجع الذاكرة مختلفة! عند تمرير دالة إلى مكون ابن، يظن الابن أن البيانات تغيرت فيعيد رسم نفسه. يقوم useCallback بتخزين مرجع الدالة نفسه وتثبيته في الذاكرة طالما لم تتغير مصفوفة التبعيات.
| 1 | import { useState, useCallback } from 'react'; |
| 2 | |
| 3 | function ParentComponent() { |
| 4 | const [count, setCount] = useState(0); |
| 5 | |
| 6 | // تثبيت مرجع الدالة لتجنب إعادة خلقها في كل رندرة للأب |
| 7 | const handleDelete = useCallback((id) => { |
| 8 | console.log('حذف العنصر رقم:', id); |
| 9 | }, []); // مصفوفة فارغة تعني تثبيت المرجع طوال حياة المكون |
| 10 | |
| 11 | return ( |
| 12 | <div> |
| 13 | <button onClick={() => setCount(count + 1)}>زيادة العداد ({count})</button> <ExpensiveChild onDelete={handleDelete} /> |
| 14 | </div> |
| 15 | ); |
| 16 | } |
استخدام useCallback بمفرده لا يمنع المكون الابن من إعادة الرندرة! يجب تغليف المكون الابن بـ React.memo ليقرأ المرجع الميثق ويمتنع عن الرندرة:
| 1 | import { memo } from 'react'; |
| 2 | |
| 3 | // المكون لن يعاد رسمه عند تغير عداد الأب بفضل تثبيت مرجع onDelete |
| 4 | const ExpensiveChild = memo(({ onDelete }) => { |
| 5 | console.log('رندرة المكون الابن...'); |
| 6 | return <button onClick={() => onDelete(1)}>حذف</button>; |
| 7 | }); |
⚠️ تحذير
لا تقم بتغليف كل دالة بـ useCallback وكل حساب بـ useMemo عشوائياً!
ℹ️ معلومة
السبب: الهوكس نفسها تحجز مساحة في الذاكرة وتستهلك معالجة لمقارنة التبعيات (Dependency Array Comparison) في كل رندرة. إذا كانت العملية بسيطة، ستكون تكلفة تشغيل الهوك أعلى من تكلفة إعادة العمل البسيط نفسه!