🚀 تحسين الأداء مع useMemo: كيف تمنع ريأكت من تكرار العمليات المرهقة؟

في الدروس السابقة، تعلمنا كيف ندير الحالة (State) وكيف نتفاعل مع دورة حياة المكونات. اليوم سننتقل إلى مستوى جديد وهو "تحسين الأداء" (Performance Optimization).

أحياناً، يقوم ريأكت بإعادة تشغيل (Re-render) للمكونات بشكل متكرر، وهذا أمر طبيعي. لكن، ماذا لو كان المكون يحتوي على عملية حسابية "ثقيلة" أو "مرهقة" تأخذ وقتاً طويلاً؟ هنا يأتي دور البطل useMemo. 🦸‍♂️


❓ ما هو useMemo ببساطة؟

تخيل أنك تقوم بعملية حسابية معقدة جداً تستغرق دقيقة كاملة لإخراج النتيجة. إذا سألك شخص ما عن النتيجة بعد ثوانٍ، هل ستقوم بإعادة الحساب من الصفر؟ بالطبع لا! ستقوم بإعطائه "النتيجة التي حفظتها في ذاكرتك" من المرة الأولى.

هذا بالضبط ما يفعله useMemo. هو يقوم بـ "تذكر" (Memoize) قيمة معينة، ولا يقوم بإعادة حسابها إلا إذا تغيرت المدخلات التي تعتمد عليها هذه العملية.


⚠️ لماذا نحتاج useMemo؟ (المشكلة)

في ريأكت، عندما تتغير حالة المكون (State) أو الخصائص (Props)، يتم إعادة تنفيذ الدالة البرمجية للمكون بالكامل. إذا كان لديك دالة تقوم بعملية حسابية كبيرة داخل المكون، فسيتم تنفيذ هذه العملية في كل مرة يتم فيها تحديث المكون، حتى لو لم تكن العملية مرتبطة بالتغيير الذي حدث!

هذا يؤدي إلى بطء في التطبيق وتجربة مستخدم سيئة. 📉


🛠️ كيف نستخدم useMemo؟ (الحل)

يأخذ useMemo وسيطين (Arguments):

  1. دالة (Function): تحتوي على العملية الحسابية التي نريد تنفيذها وإرجاع قيمتها.
  2. مصفوفة التبعيات (Dependencies Array): قائمة بالمتغيرات التي إذا تغير أحدها، يجب على useMemo إعادة حساب القيمة.

الصيغة الأساسية:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

💻 مثال عملي وبسيط

لنفرض أن لدينا تطبيقاً يحتوي على "عداد" (Counter) وعملية حسابية بسيطة لكننا سنعتبرها "ثقيلة" لغرض الشرح.

بدون استخدام useMemo (الأداء سيء): في هذا المثال، في كل مرة تضغط فيها على زر زيادة العداد، ستعمل دالة calculateSum حتى لو لم يتغير الرقم الذي نجمع منه!

import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(10);

  // هذه الدالة سيتم تنفيذها في كل مرة يتغير فيها العداد (count)
  // وهذا غير منطقي لأنها تعتمد فقط على (number)
  const doubleNumber = number * 2; 
  console.log("Calculating double number...");

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <p>Double of {number} is: {doubleNumber}</p>
    </div>
  );
}

export default App;

باستخدام useMemo (الأداء ممتاز): الآن، سنخبر ريأكت: "لا تعيد حساب doubleNumber إلا إذا تغيرت قيمة number فقط".

import React, { useState, useMemo } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(10);

  // الآن سيتم الحساب فقط عندما تتغير قيمة number
  // إذا تغير الـ count، ريأكت سيعطينا القيمة المحفوظة فوراً دون إعادة الحساب
  const doubleNumber = useMemo(() => {
    console.log("Calculating double number... ⚙️");
    return number * 2;
  }, [number]); // التبعية هي number فقط

  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(count + 1)}>Increase Count</button>
      <p>Double of {number} is: {doubleNumber}</p>
      <button onClick={() => setNumber(number + 1)}>Increase Number</button>
    </div>
  );
}

export default App;

📝 ملاحظات ذهبية للمبتدئين

  1. لا تستخدم useMemo في كل مكان: استخدام هذه الأداة له تكلفة بسيطة في الذاكرة. استخدمها فقط مع العمليات الحسابية التي تلاحظ أنها تبطئ التطبيق. ⚠️
  2. مصفوفة التبعيات: إذا تركت المصفوفة فارغة [] سيتم حساب القيمة مرة واحدة فقط عند تشغيل التطبيق لأول مرة.
  3. الفرق بينها وبين useEffect: الـ useMemo هدفها هو إرجاع قيمة وحفظها، بينما useEffect هدفها تنفيذ "أثر جانبي" (مثل جلب بيانات من API) ولا ترجع قيمة.