Menu
flag Ar iconالعربيةdown icon

الخطاف useMemo في React: تخزين الحسابات المكلفة

يخزّن useMemo نتيجة عملية حسابية بين مرات العرض ولا يعيد حسابها إلا عندما تتغير اعتمادياتها. تعلّم الصيغة، وكيف تُبقي كائنًا ثابتًا لابن ملفوف بـ memo، ومتى لا يستحق useMemo العناء.

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

يخزّن useMemo نتيجة عملية حسابية بين مرات العرض. تشغّل React دالتك في العرض الأول، وتحتفظ بالنتيجة، وتعيد النتيجة نفسها في مرات العرض اللاحقة حتى تتغير إحدى القيم في مصفوفة الاعتماديات.

افتح Console وبدّل السمة بضع مرات: لا يُسجَّل شيء، ويبدو التبديل فوريًا. بدّل التبويبات فيظهر filtering for active، مع توقف قصير، لأن tab اعتمادية. استبدل الآن السطر useMemo بـ const visible = filterTodos(todos, tab); وبدّل السمة مرة أخرى: كل نقرة تسجّل وتتوقف 100 جزء من الثانية.

الصيغة

const cachedValue = useMemo(calculateValue, dependencies);

const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  • calculateValue دالة بلا وسائط تُرجع القيمة التي تريد تخزينها. تستدعيها React أثناء العرض.
  • dependencies قائمة بكل قيمة من المكوّن تقرؤها الدالة: props والحالة والمتغيرات المعلنة في جسم المكوّن.
  • في كل عرض تقارن React كل اعتمادية بقيمتها من العرض السابق باستخدام Object.is. إذا تطابقت كلها، تُرجع القيمة المخزنة دون استدعاء دالتك. وإذا اختلفت أي منها، تستدعي الدالة وتخزّن النتيجة الجديدة.

useMemo خطاف، فيتبع قواعد الخطافات: استدعِه في المستوى الأعلى من المكوّن، لا داخل حلقة أو شرط.

يجب أن تكون الاعتماديات كاملة

احذف قيمة من المصفوفة فيظل التخزين يقدّم نتيجة محسوبة من قيمتها القديمة. هنا يقرأ المجموع qty لكنه لا يسرد إلا price:

انقر Quantity: تصبح 2، لكن المجموع يبقى 10 ولا يُسجَّل شيء. انقر Price فيقفز المجموع إلى 22، لأن العملية الحسابية لا تعمل من جديد إلا حينها، وتقرأ الآن qty الحالية. أضف qty إلى المصفوفة فيحدّث الزران كلاهما المجموع. تلتقط قاعدة الفحص react-hooks/exhaustive-deps هذا الخطأ في مشروع حقيقي.

والخطأ المعاكس اعتمادية جديدة في كل عرض، مثل كائن يُنشأ في جسم المكوّن. لا تطابق قيمتها السابقة أبدًا، فلا يُستخدم التخزين أبدًا:

const filters = { tab, query }; // a new object every render
const visible = useMemo(() => applyFilters(todos, filters), [todos, filters]); // recomputes every time

// List the primitive values instead
const visible = useMemo(() => applyFilters(todos, { tab, query }), [todos, tab, query]);

إبقاء كائن ثابتًا لابن ملفوف بـ memo

السبب الثاني للجوء إلى useMemo لا علاقة له بالحسابات البطيئة. الابن الملفوف بـ memo لا يتخطى عرضه إلا عندما تكون كل prop كما كانت في المرة السابقة. والكائن الحرفي المكتوب في جسم المكوّن كائن جديد في كل عرض، فيُبطل memo حتى عندما لا تتغير محتوياته. يُبقي useMemo الكائن نفسه حتى تتغير مدخلاته.

اكتب في حقل الملاحظة فيبقى Chart صامتًا، بينما يسجّل Switch unit عرضًا واحدًا. استبدل استدعاء useMemo بـ const options = { unit, max: unit === 'km' ? 100 : 62 }; عادي فتسجّل كل ضغطة مفتاح في الملاحظة render Chart.

وينطبق الأمر نفسه على تأثير يعتمد على كائن. إذا سرد تأثير options في اعتمادياته وكانت options تُبنى من جديد في كل عرض، يعمل التأثير بعد كل عرض. يصلح useMemo ذلك، لكن الإصلاح الأبسط غالبًا هو إنشاء الكائن داخل التأثير والاعتماد على القيم البسيطة:

useEffect(() => {
    const options = { serverUrl, roomId };
    const connection = createConnection(options);
    connection.connect();
    return () => connection.disconnect();
}, [serverUrl, roomId]);

متى لا تستخدم useMemo

معظم العمليات الحسابية في المكوّن رخيصة: جمع أرقام، أو ضم نصين، أو ترشيح بضع عشرات من العناصر. تخزين هذه لا يوفر شيئًا يُذكر: ما زالت React تخزن القيمة وتقارن الاعتماديات في كل عرض، ويصبح الكود أصعب قراءة.

// No useMemo needed
const fullName = first + ' ' + last;
const doneCount = todos.filter((t) => t.done).length;

لتعرف ما إذا كان شيء ما بطيئًا، قِس وقته. غلّف العملية الحسابية بـ console.time('filter') وconsole.timeEnd('filter')، ونفّذ التفاعل، وانظر إلى الرقم. وكدليل تقريبي، العمل الذي يستغرق جزءًا من الثانية أو أكثر على جهازك مرشح، مع مراعاة أن أجهزة مستخدميك قد تكون أبطأ. وقبل اللجوء إلى useMemo، تحقق أيضًا مما إذا كان العمل ينتمي إلى العرض أصلًا: البيانات التي لا تتغير أبدًا يمكن حسابها مرة واحدة خارج المكوّن، والحالة التي لا يستخدمها إلا جزء صغير من الصفحة يمكن نقلها إلى مكوّن أصغر (راجع العرض).

useMemo مقابل useEffect للقيم المشتقة

من الأنماط الشائعة حساب قيمة في تأثير وتخزينها في الحالة:

// Two renders per change, and the first one has stale data
const [visible, setVisible] = useState([]);
useEffect(() => {
    setVisible(filterTodos(todos, tab));
}, [todos, tab]);

// One render, always in sync
const visible = useMemo(() => filterTodos(todos, tab), [todos, tab]);

تُعرض نسخة التأثير مرة بقيمة visible القديمة، ثم يعمل التأثير، فيضبط الحالة ويعيد العرض. هذا عرض إضافي، وفي الأول منهما لا تطابق القائمة التبويب. أما الحساب أثناء العرض بـ useMemo (أو دون أي خطاف، إذا كان العمل رخيصًا) فيعطي القيمة الصحيحة في العرض نفسه. التأثيرات للمزامنة مع أشياء خارج React، لا لاشتقاق قطعة حالة من أخرى.

تلميح للأداء لا ضمان

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

في التطوير، مع StrictMode، تستدعي React دالة الحساب مرتين لتساعدك على اكتشاف الكود غير النقي. تعمل المعاينة هنا مثل بناء الإنتاج، فسترى كل سجل filtering for مرة واحدة.

useMemo وReact Compiler

React Compiler 1.0 أداة بناء تحلل مكوّناتك وتضيف الحفظ تلقائيًا، فتخزّن الحسابات وJSX في المواضع التي كان سيفيد فيها useMemo وuseCallback وmemo. وفي مشروع يستخدمه، ستكتب عددًا أقل منها يدويًا. يظل useMemo اليدوي يعمل بجانب المترجم، وما زال الأداة المناسبة عندما لا يكون المترجم مُعدًّا، وستصادفه في معظم كود React الموجود.

الأسئلة الشائعة

ماذا يفعل useMemo في React؟

يستدعي دالة الحساب الخاصة بك في العرض الأول، ويخزّن النتيجة، وفي مرات العرض اللاحقة يُرجع النتيجة المخزنة ما دامت كل قيمة في مصفوفة الاعتماديات كما كانت في المرة السابقة (بالمقارنة عبر Object.is).

متى أستخدم useMemo؟

عندما تكون عملية حسابية بطيئة بشكل ملحوظ ونادرًا ما تتغير مدخلاتها، أو عندما تحتاج إلى أن يحتفظ كائن أو مصفوفة بالمرجع نفسه بين مرات العرض لأن ابنًا ملفوفًا بـ memo أو تأثيرًا يعتمد عليه. أما للعمل الرخيص فتخطَّه.

ما الفرق بين useMemo وuseCallback؟

يخزّن useMemo القيمة التي تُرجعها دالتك. ويخزّن useCallback الدالة نفسها. وuseCallback(fn, deps) هي نفسها useMemo(() => fn, deps).

هل يضمن useMemo ألا يُعاد حساب القيمة أبدًا؟

لا. إنه تلميح للأداء. قد ترمي React القيمة المخزنة في بعض الحالات، مثلًا في التطوير عندما تعدّل الملف، لذا يجب أن يظل كودك يعمل بشكل صحيح إذا عملت العملية الحسابية من جديد.

هل ما زلت أحتاج إلى useMemo مع React Compiler؟

يستطيع React Compiler إضافة الحفظ تلقائيًا عندما يستخدمه البناء لديك. ومن دون المترجم، أو في الحالات التي يتخطاها، يظل useMemo اليدوي يعمل بالطريقة نفسها وما زال شائعًا في الكود الموجود.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن