Kodokon kodokon.com

المذاكرة (Memoization): memo و useMemo و useCallback

تعلّم متى تُسرّع React.memo و useMemo و useCallback تطبيقك فعلاً، ومتى تُعقّده بلا طائل.

10 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

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

JSX
import { memo } from "react";

const Row = memo(function Row({ item, onSelect }) {
  return (
    <li onClick={() => onSelect(item.id)}>{item.label}</li>
  );
});
Row يُعاد تصييره فقط إذا تغيّر مرجع item أو onSelect.

قوة السلسلة من قوة أضعف حلقاتها. إذا تلقّت prop واحدة مرجعًا جديدًا في كل تصيير - دالة سهم مضمّنة، أو كائن أو مصفوفة حرفية - فشلت مقارنة memo وأُعيد تصيير المكوّن على أي حال. يُثبّت useCallback مرجع الدالة كي تنجح المقارنة.

JSX
import { useCallback } from "react";

function List({ items }) {
  const handleSelect = useCallback((id) => {
    console.log("selected:", id);
  }, []);

  return (
    <ul>
      {items.map((item) => (
        <Row
          key={item.id}
          item={item}
          onSelect={handleSelect}
        />
      ))}
    </ul>
  );
}
handleSelect يحتفظ بنفس المرجع في كل تصيير.

لـ useMemo استخدامان مشروعان: تفادي حساب باهظ فعلاً (فرز أو تجميع آلاف العناصر)، وتثبيت مرجع يستهلكه ابن مُذاكَر أو مُدرَج في تبعيات تأثير. أما بالنسبة لحساب تافه، فالتخزين المؤقّت يكلّف أكثر مما يوفّر: مقارنة التبعيات، وذاكرة محتجزة، وقراءة أثقل.

JSX
import { useMemo } from "react";

function Stats({ orders }) {
  const total = useMemo(() => {
    return orders.reduce(
      (sum, order) => sum + order.amount,
      0
    );
  }, [orders]);

  return <p>Total: {total} €</p>;
}
لا يُعاد حساب الإجمالي إلا إذا تغيّر مرجع orders.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. ابن مُغلَّف بـ React.memo، لكن أباه يمرّر له دالة سهم مضمّنة كـ prop. النتيجة؟
    • المذاكرة تعمل: تُقارَن الدوال بشيفرتها المصدرية
    • المذاكرة غير فعّالة: الدالة المضمّنة تُنشئ مرجعًا جديدًا في كل تصيير وتفشل المقارنة
    • ترفع React تحذيرًا في وضع التطوير
  2. ماذا يضمن useCallback(fn, [a]) طالما لم يتغير a؟
    • أن الدالة لا يُعاد إنشاؤها أبدًا في الذاكرة
    • أن الدالة لن تُنفَّذ إلا مرة واحدة
    • أن نفس مرجع الدالة يُرجَع من تصيير إلى آخر
  3. في أي حالة يكون useMemo مبررًا بوضوح؟
    • حساب باهظ، أو مرجع يجب تثبيته لابن مُذاكَر أو لتبعية تأثير
    • أي قيمة مشتقة، من باب الاحتياط
    • تخزين البيانات عبر تركيبين للمكوّن