أين تذهب أداء تطبيقك؟
هذا أول سؤال أطرحه على عميل جديد يأتيني يقول: "تطبيقنا بطيء." وتسع مرات من أصل عشر، الإجابة هي: "لا نعرف."
معظم فريق تطوير React في الكويت والخليج لا يستخدم Profiler. هم يكتبون الكود، يتوقعون أن يكون سريعاً، ثم عندما يبدأ المستخدمون يشكون، يتفاجأون. وفي هذه اللحظة، يبدأون ينظرون إلى memo و lazy و Suspense وكأنها حل سحري سينقذهم. لكن هذا ليس الطريق الصحيح.
أنا لست هنا لأخبرك بـ "أفضل الممارسات العامة." أنت سمعتها مئات المرات. أنا هنا لأقول لك الحقيقة: الأداة الحقيقية ليست الكود، هي القياس.
المشكلة ليست في React
دعني أبدأ بحقيقة قد تفاجئك: React نفسه سريع جداً. المحرك الأساسي يعيد تصيير مكون وأطفاله في ميلي ثانية واحدة تقريباً، حتى لو كان المكون معقداً. لكن تطبيقك قد يشعر بالبطء لسبب بسيط: أنت تعيد تصيير أشياء لا تحتاج إلى إعادة تصيير.
تخيل لوحة تحكم لشركة استشارات في الكويت. مئات العملاء والمشاريع والفواتير. زر واحد في الزاوية العلوية يغير اللغة من الإنجليزية إلى العربية. بسيط جداً، أليس كذلك؟
لكن عندما يضغط الزر، ماذا يحدث؟ المكون الأب (GlobalProvider) يتحدث عن اللغة الجديدة. كل مكون فرعي يعاد تصييره — حتى الجداول الضخمة التي لا تتغير. كل صف في الجدول يعاد تصييره — حتى لو كانت معاودة التصيير لا تغير شيئاً مرئياً.
والنتيجة؟ تجميد لمدة ثانية كاملة. ليس لأن React بطيء. بل لأنك تطلب منه إعادة رسم 5000 صف في جدول بدون أي سبب حقيقي.
memo — متى تستخدمها والخطأ الذي تقع فيه كل الفرق
React.memo أداة بسيطة جداً في الواقع: إذا كانت الخاصيات (props) المُمررة إلى مكون لم تتغير، لا تعيد تصييره. نقطة.
الخطأ الكلاسيكي
أنت تحرس المكون بـ memo ثم تمرر دالة جديدة (`onDelete={() => ...}`) في كل عملية تصيير. memo ترى تغيير في الخاصيات (لأن الدالة مرجع جديد) وتقول "سأعيد التصيير على كل حال." memo أصبحت عديمة الفائدة وأنت لا تلاحظ.
رأيت هذا الخطأ بالضبط يُغرق مشاريع كانت ممولة تمويلاً جيداً في السعودية والإمارات. فريق يكتب memo في كل مكان، يشعرون بأنهم "يحسّنون الأداء" بينما هم في الحقيقة يضيفون طبقة معقدة بدون فائدة.
الحل الصحيح هو استخدام `useCallback` لضمان أن الدوال المُمررة تبقى نفسها عبر عمليات التصيير:
```javascript
const ParentList = ({ items, onDeleteItem }) => {
const handleDelete = useCallback((id) => {
onDeleteItem(id);
}, [onDeleteItem]);
return (
<div>
{items.map(item => (
<BlogCard
key={item.id}
data={item}
onDelete={() => handleDelete(item.id)}
/>
))}
</div>
);
};
```
الآن، `handleDelete` تبقى نفسها طالما `onDeleteItem` لم تتغير. أخيراً، memo ستعمل كما توقعت.
لكن دعني أكون صريحاً: معظم الشركات في الكويت لا تحتاج إلى memo في كل مكان. أنت تحتاج إليها عندما يكون عندك مكون ثقيل (شجرة معقدة من الـ children) ويعاد تصييره مئات المرات في الثانية وخاصياته نادراً ما تتغير. إذا كان مكونك بسيطاً، memo قد تضيف مزيداً من التعقيد بدون فائدة حقيقية.
lazy و Suspense — تأخير ذكي، لا كسل
React.lazy تقول لك: "لا تحمل هذا المكون الآن. حمّله عندما يحتاج المستخدم إليه فقط."
مثال واقعي من سوق الخليج: لوحة تحكم مشروع في وكالة تسويق رقمية. تحتوي على ثلاثة أقسام رئيسية — جدول التقارير (ضخم جداً، 2 MB بدون ضغط)، قسم الإعدادات (صغير وبسيط)، ومراقب الأداء (معقد جداً مع مئات الرسوم البيانية).
لماذا تحمل جدول التقارير بكامل حجمه عندما يفتح المستخدم الإعدادات أولاً؟ ستُضيع 2 ثانية إضافية على التحميل الأولي.
```javascript
const Analytics = React.lazy(() => import('./Analytics'));
const Reports = React.lazy(() => import('./Reports'));
const Dashboard = ({ activeTab }) => (
<Suspense fallback={<div>جاري التحميل...</div>}>
{activeTab === 'reports' && <Reports />}
{activeTab === 'analytics' && <Analytics />}
</Suspense>
);
```
الآن عندما يضغط المستخدم على "التقارير" بعد دقائق، يبدأ التحميل في الخلفية. في هذه الأثناء، Suspense تظهر رسالة "جاري التحميل..." بدل شاشة معلقة تماماً.
النتيجة الحقيقية من تطبيق في السعودية استخدم هذا النمط: الصفحة الأولى تحمل في 1 ثانية بدل 4 ثوان. والمستخدم يرى شيئاً يحدث (رسالة التحميل) بدل شاشة معلقة بدون ردود فعل.
Profiler — أداتك الحقيقية للكشف
هنا يأتي الجزء الأهم: القياس. لا الحدس، لا التخمين. القياس الفعلي.
افتح Chrome DevTools. اذهب إلى Components tab. ابحث عن Profiler. اضغط على الزر الأحمر Record. الآن تفاعل مع تطبيقك — غيّر قيمة، اضغط زراً، مرر سهم الماوس. ثم اضغط الزر الأحمر مرة أخرى.
الآن تراني:
أولاً، رسم بياني للتصيير (Render Duration) — كم ميلي ثانية استغرقت عملية التصيير كاملة؟ ثانياً، قائمة المكونات (Component List) — أي مكون استغرق أطول وقت؟ ثالثاً، سبب إعادة التصيير (Why Did This Render) — لماذا أعاد React تصيير هذا المكون بالضبط؟
من تجربتي، هذه الأداة عثرت على الأخطاء التي لا يمكن لأحد أن يخمنها بدون بيانات:
- مكون صغير يبدو بريئاً (زر "Save") يعيد تصييره 10 مرات في الثانية لأن والده يمرر دالة جديدة في كل مرة.
- قائمة منسدلة تعاد رسمها بالكامل في كل كبسة مفتاح لأن المطور كتب `filter(items)` داخل الـ render بدل `useMemo`.
- جدول ضخم يتجمد لأن كل صف يقوم بطلب API — أنت لا تقرأ الأخطاء في الـ console، لكن DevTools ترى 1000 طلب معلق.
تطبيق حقيقي: حالة من الكويت
قبل سنة، أتاني عميل في الكويت — شركة توظيف طورت تطبيقاً ضخماً في React. الشكوى المعتادة: "بطيء جداً. هل نحتاج إلى أداة أفضل؟"
فتحت Profiler. البيانات التي رأيتها:
- التصيير الواحد يستغرق 850 ميلي ثانية
- 80% منها في مكون واحد: JobFilters
- المشكلة الفعلية: كل مرة يكتب المستخدم حرفاً واحداً في حقل البحث، يعاد تصيير جدول 5000 وظيفة بالكامل
الحل لم يكن تعقيداً. كان مجرد فصل الاهتمامات بشكل صحيح.
النتيجة بعد التصحيح؟ من 850 ميلي ثانية إلى 150. السرعة زادت بـ 5 مرات ونصف. ليس من خلال تغيير الأداة أو الطلب من الخادم أن يكون أسرع. مجرد قياس وفهم.
ملاحظة شخصية
حين يأتيني عميل يسأل عن الأداء، أول سؤال أطرحه عليه ليس "هل استخدمت memo؟" بل "هل فتحت Profiler قط؟" التسعة الذين قالوا لا يقفون في الزاوية بخجل. الواحد الذي قال نعم — تلك شركة جيدة تعرف ما تفعل.
الخطوات العملية لتطبيقك
لا تبدأ بـ memo في كل مكان. هذا الترتيب:
الخطوة 1: افتح Profiler
تطبيقك في Development mode (ليس Production — Development mode يعطيك رسائل تحذير مفيدة جداً).
الخطوة 2: سجل تفاعل حقيقي
اكتب نصاً، غيّر فلتراً، مرر الماوس. أي تفاعل يشكو منه المستخدمون.
الخطوة 3: اقرأ النتائج
ما المكون الأبطأ؟ كم مرة أعاد التصيير؟ هل كان هناك سبب معقول؟
الخطوة 4: ثم فقط، عدّل الكود
استخدم memo أو useMemo أو useCallback بناءً على ما رأيت في البيانات، لا بناءً على حدسك.
الواقع الذي قد لا يعجبك
بصراحة، معظم مشاكل الأداء ليست مشاكل JavaScript على الإطلاق. أنت تحتاج إلى:
قاعدة بيانات أفضل (ليس كود JavaScript أنيق). طلبات API أقل (ليس memo). صور محسّنة بشكل صحيح (ليس Suspense). شبكة أسرع (ليس useCallback).
React Profiler ستخبرك إذا كانت المشكلة في الكود JavaScript فعلاً. إذا كانت ستخبرك بوضوح. إذا لم تكن، قد تكون قاعدة بيانات الخادم بطيئة أو API endpoint معيب أو الصور كبيرة جداً.
الخطوة الحقيقية الأولى؟ قياس. دائماً قياس قبل أن تفترض.
الملخص البسيط
تطبيقك ليس بطيئاً لأن React بطيء. تطبيقك بطيء لأنك تطلب من React إعادة رسم أشياء لا تحتاج إلى إعادة رسم. والطريقة الوحيدة لمعرفة ما هو بالفعل مصدر البطء هي استخدام Profiler.
memo و lazy و Suspense ليست تعقيد ولا سحر. هي أدوات بسيطة عندما تستخدمها في المكان الصحيح بناءً على بيانات حقيقية.
خذ ساعة من وقتك الآن. افتح Profiler. سجل تفاعلاً واحداً بطيئاً. اقرأ النتائج. ستفاجأ كم بسيطة الحل عندما تعرف المشكلة الحقيقية.