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

React.memo: تخطي إعادة العرض عندما تكون props نفسها

يغلّف memo مكوّنًا فتتخطى React إعادة عرضه عندما تكون props الخاصة به متساوية سطحيًا مع المرة السابقة. تعلّم متى يفيد، ولماذا تُبطله الكائنات والدوال الجديدة في props، وما الذي لا يوقفه.

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

يتيح memo لمكوّن أن يتخطى إعادة العرض عندما تكون props الخاصة به كما كانت في المرة السابقة. غلّف مكوّنًا بـ memo(...)، وعندما يُعرض أبوه تقارن React أولًا كل prop جديدة بالقديمة؛ وإذا كانت كلها متساوية، تعيد استخدام النتيجة السابقة بدلًا من استدعاء المكوّن من جديد.

اكتب في الحقل: يواكبك، وتُظهر Console render SlowList مرة واحدة فقط، من العرض الأول. احذف المغلّف memo( (وقوس إغلاقه) واكتب مرة أخرى: كل ضغطة مفتاح تنتظر الآن 150 جزءًا من الثانية للقائمة وتسجّل عرضًا.

الصيغة

import { memo } from 'react';

const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
  • SomeComponent أي مكوّن دالة. والأسلوب المعتاد هو تغليف التعريف مباشرة: const Card = memo(function Card(props) { ... }). تسمية الدالة الداخلية تُبقي الاسم في React DevTools وفي رسائل الأخطاء.
  • arePropsEqual اختيارية ونادرًا ما تحتاج إليها (انظر أدناه).
  • النتيجة مكوّن تعرضه مثل أي مكوّن آخر.

افتراضيًا تقارن React props القديمة والجديدة مقارنة سطحية: تفحص كل prop بـ Object.is. تُقارن النصوص والأرقام والقيم المنطقية بالقيمة، فيساوي title="Hi" القيمة title="Hi". أما الكائنات والمصفوفات والدوال فتُقارن بالمرجع: كائنان بالمحتويات نفسها ما زالا كائنين مختلفين.

في المثال أعلاه، items معلنة خارج المكوّن، فهي المصفوفة نفسها في كل عرض، وتنجح المقارنة.

الكائنات والدوال الجديدة تُبطل memo

كل ما يُكتب كقيمة حرفية داخل جسم الأب يُنشأ من جديد في كل عرض: { name: 'Ada' } و[1, 2, 3] و() => save(). مرّر واحدًا منها إلى مكوّن محفوظ فلا تساوي الـ prop السابقة أبدًا.

كل نقرة تسجّل render Badge object prop لكن لا تسجّل نسخة النص أبدًا. هناك ثلاث طرق لإصلاح حالة الكائن:

  • مرّر قيمًا بسيطة. name="Ada" بدلًا من user={{ name: 'Ada' }} عندما لا يحتاج الابن إلا إلى حقل أو اثنين.
  • انقل الثوابت خارج المكوّن. الكائن الذي لا يتغير أبدًا يمكن أن يعيش على مستوى الوحدة، مثل items في المثال الأول.
  • خزّنه. غلّف الكائنات والمصفوفات المبنية من props أو الحالة بـ useMemo، والدوال بـ useCallback.

ولـ children المشكلة نفسها. ينشئ <Card><p>Hello</p></Card> كائن عنصر <p> جديدًا في كل عرض، فيُعاد عرض Card المحفوظ الذي يستقبل أبناء في كل مرة.

ما الذي لا يوقفه memo

لا ينظر memo إلا إلى props الآتية من الأب. ما زال المكوّن المحفوظ يُعرض عندما:

  • تتغير حالته الخاصة، عبر useState أو useReducer الخاص به؛
  • يتغير سياق يقرؤه، عبر useContext.

انقر Unrelated فيُتخطى Panel. بدّل السمة فيُعرض، لأنه يقرأ السياق. وانقر Like فيُعرض، لأن حالته الخاصة تغيّرت. هذا هو السلوك المقصود: memo يتعلق بتخطي العمل عندما لا يتغير شيء يعتمد عليه المكوّن، والسياق والحالة من الأشياء التي يعتمد عليها.

دالة مقارنة مخصصة

يستبدل الوسيط الثاني المقارنة السطحية. يستقبل props السابقة والتالية ويُرجع true عندما ينبغي معاملتها كمتساوية، أي تتخطى React العرض.

const MemoChart = memo(Chart, (prev, next) => {
    return (
        prev.points.length === next.points.length &&
        prev.points.every((p, i) => p === next.points[i]) &&
        prev.color === next.color &&
        prev.onSelect === next.onSelect
    );
});

نادرًا ما تحتاج إلى هذا، ومن السهل أن تخطئ فيه. إذا تجاهلت الدالة prop، يظل المكوّن يعرض القيمة القديمة لتلك الـ prop. وتجاهل prop من نوع دالة هو أسوأ حالة: يظل المكوّن المتخطى يستدعي الدالة القديمة التي تقرأ حالة قديمة. قارن كل prop، ولا تجرِ فحوص مساواة عميقة على بيانات كبيرة، لأن المقارنة حينها تكلف أكثر من العرض الذي توفره. والـ props الثابتة عبر useMemo وuseCallback هي الإصلاح الأفضل في كل الحالات تقريبًا.

التحقق من أن memo يعمل

أسرع فحص هو المستخدم في هذه الصفحة: console.log في أعلى المكوّن. إذا طبع عندما توقعت تخطيًا، فسجّل props أيضًا، وقارنها بالسابقة لتجد التي تغيّرت. وفي مشروع حقيقي تساعد React DevTools أكثر: فعّل "Highlight updates when components render" في إعداداتها لترى أي المكوّنات تومض مع كل تفاعل، ويسجّل تبويب Profiler كل عرض، ومع تفعيل الإعداد المناسب، سبب عرض كل مكوّن.

أمران يبدوان كفشل لـ memo وليسا كذلك:

  • العرض الأول يحدث دائمًا. لا يتخطى memo العرض إلا بعد أن يُعرض المكوّن مرة بتلك props.
  • قد تعرض بنيات التطوير أكثر. مع StrictMode تعرض React كل مكوّن مرتين في التطوير لتلتقط الكود غير النقي. تعمل المعاينة هنا مثل بناء الإنتاج، فكل سطر سجل أعلاه يعني عرضًا حقيقيًا واحدًا.

متى تستخدم memo

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

يحفظ React Compiler 1.0، إذا كان مشروعك يستخدمه، المكوّنات وprops الخاصة بها تلقائيًا وقت البناء، ما يجعل معظم استدعاءات memo اليدوية غير ضرورية. ومن دونه، يعمل memo تمامًا كما هو موصوف هنا ويبقى شائعًا في كود React.

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

ماذا يفعل React.memo؟

يُرجع نسخة محفوظة من مكوّن. عندما يُعرض الأب، تقارن React props الجديدة بالسابقة وتتخطى عرض المكوّن إذا كانت كل prop متساوية بحسب Object.is.

لماذا ما زال مكوّن memo الخاص بي يُعاد عرضه؟

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

ما الفرق بين React.memo وuseMemo؟

يغلّف memo مكوّنًا ويتخطى عرضه. أما useMemo فخطاف داخل مكوّن يخزّن قيمة محسوبة. وكثيرًا ما يعملان معًا: يُبقي useMemo prop من نوع كائن ثابتة ليتمكن memo من التخطي.

هل أغلّف كل مكوّن بـ memo؟

لا. معظم مرات العرض رخيصة، ولمقارنة props تكلفتها الخاصة. استخدمه للمكوّنات البطيئة في العرض التي تستقبل غالبًا props نفسها.

هل يعمل memo مع children؟

نادرًا. JSX الممررة على أنها children كائن جديد في كل عرض، فالمكوّن المحفوظ الذي يستقبل أبناء يُعاد عرضه دائمًا تقريبًا.

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

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

ابدأ الآن