يقوم خطاف useMemo في React Native بتخزين نتيجة الحسابات بين عمليات إعادة عرض المكون، مما يمنع التنفيذ المتكرر للعمليات المكلفة. على عكس useCallback الذي يخزّن الدالة، يحتفظ useMemo بالقيمة المعادة ويعيد حسابها فقط عند تغير التبعيات المحددة. وفقًا لـ React Docs, 2024، يكون التخزين المؤقت فعالاً بشكل خاص للحسابات ذات المصفوفات الكبيرة وتحويلات البيانات المعقدة. في React Native، كل إعادة عرض غير ضرورية تُنشئ حملًا إضافيًا على اتصال الجسر بين JavaScript والمسارات الأصلية، لذا فإن الاستخدام الصحيح لـ useMemo يؤثر مباشرة على سلاسة الرسوم المتحركة وسرعة استجابة الواجهة.
النقاط الرئيسية
useMemo — هو خطاف من المكتبة الأساسية لـ React، متاح في React Native بدون حزم إضافية. يخزّن القيمة المعادة من الدالة المُمررة ويعيد استخدام هذه القيمة في عمليات إعادة العرض اللاحقة حتى تتغير التبعيات المحددة في المصفوفة.
اسم الخطاف مشتق من memoization — تقنية تحسين حيث يتم تخزين نتيجة دالة مكلفة في ذاكرة تخزين مؤقت. عند الاستدعاءات اللاحقة بنفس الوسائط، يتم إرجاع القيمة المخزنة بدلاً من إعادة الحساب.
في سياق React Native، التخزين المؤقت مهم بشكل خاص بسبب الخصائص المعمارية. كل إعادة عرض للمكون ترسل بيانات عبر جسر JavaScript — الأصلي، مما يستغرق وقتًا. إذا كان المكون يحتوي على حسابات مكلفة (تصفية قائمة، فرز، تنسيق)، فإن كل إعادة عرض ستكررها، مما يعوق مسار JavaScript ويسبب انخفاض الإطارات.
وفقًا لـ React Native Performance Docs, 2024، غالبًا ما تكون عنق الزجاجة ليست في عرض المكونات الأصلية، بل في تنفيذ منطق JavaScript بين عمليات إعادة العرض. useMemo يحل هذه المشكلة تحديدًا.
useMemo يستقبل دالة ومصفوفة تبعيات، ويعيد قيمة مخزنة مؤقتًا. يخزن React القيمة السابقة ومصفوفة التبعيات. في كل إعادة عرض، يقارن React التبعيات الحالية مع السابقة باستخدام Object.is. إذا تغير تبعية واحدة على الأقل، يتم تنفيذ الدالة مرة أخرى ويُخزَّن الناتج.
توقيع useMemo متطابق على الويب وفي React Native. الوسيط الأول هو دالة بدون معلمات تُرجع القيمة المراد تخزينها. الوسيط الثاني هو مصفوفة تبعيات، عند تغيرها سيتم إعادة حساب القيمة.
import React, { useMemo } from 'react';
const sortedList = useMemo(() => {
return items.sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
في هذا المثال، يتم إعادة حساب sortedList فقط عندما يتغير المرجع إلى مصفوفة items. إذا تغير محتوى المصفوفة لكن المرجع بقي كما هو (تحوير)، فلن يكتشف useMemo التغييرات — وهذا قيد مهم.
يستخدم React خوارزمية Object.is لمقارنة التبعيات، والتي تعمل بشكل مشابه للمساواة الصارمة === لكنها تتعامل مع NaN بشكل صحيح (+0 و -0 يُعتبران مختلفين). هذا يعني أنه للأنواع البدائية (سلاسل نصية، أرقام، قيم منطقية)، تعمل المقارنة حسب القيمة، بينما للكائنات والمصفوفات — حسب المرجع.
// Object.is({'a': 1}, {'a': 1}) -> false (different refs)
// Object.is(42, 42) -> true (primitive by value)
// Object.is(NaN, NaN) -> true (correct handling of NaN)
فهم هذه الآلية أمر بالغ الأهمية: إذا قمت بتمرير كائن جديد في التبعيات في كل إعادة عرض، سيكون التخزين المؤقت عديم الفائدة لأن التبعيات ستُعتبر متغيرة في كل مرة.
useMemo مبرر في ثلاثة سيناريوهات: الحسابات المكلفة، تمرير props ثابتة للمكونات الفرعية، والحفاظ على الهوية المرجعية للكائنات. في React Native، تحدث السيناريوهات الثلاثة بانتظام بسبب بنية التطبيقات المحمولة.
إذا كان المكون يعالج مصفوفات كبيرة من البيانات (تصفية، فرز، تجميع) أو عمليات رياضية كثيفة الموارد، يمنع useMemo تكرار هذه الحسابات في كل إعادة عرض. في التطبيقات المحمولة، مثال نموذجي هو تنسيق البيانات من API قبل عرضها في قائمة.
عندما يكون المكون الفرعي مغلفًا بـ React.memo، فإنه يعيد العرض فقط عندما تتغير props الخاصة به. إذا تم إنشاء كائن prop داخل المكون الأب بدون useMemo، فسيتم إنشاء مرجع جديد في كل إعادة عرض للأب، وسيعيد المكون الفرعي العرض بشكل غير ضروري، مما يلغي فائدة React.memo.
في React Native، تعتمد الأنماط غالبًا على props — على سبيل المثال، عرض عنصر يُحسب بناءً على حجم الشاشة. يسمح useMemo بحساب هذه الأنماط الديناميكية فقط عند تغير معلمات الإدخال.
useMemo ليس أداة تحسين شاملة. في عدة سيناريوهات، لا يوفر فائدة بل قد يضعف الأداء بسبب الحمل الإضافي لتخزين التبعيات ومقارنتها. في React Native، حيث كل بايت من الذاكرة مهم، تطبيق useMemo بشكل أعمى دون قياس تأثيره هو نمط معاكس.
الخطاف لا يوفر فائدة في ثلاث حالات رئيسية. أولاً، إذا كان الحساب تافهًا (جمع بسيط، دمج سلاسل نصية)، فإن تكلفة useMemo تتجاوز تكلفة الحساب نفسه. ثانيًا، إذا كانت التبعيات تتغير في كل إعادة عرض — سيتم تنفيذ الدالة على أي حال، والتخزين المؤقت يضيف فقط عملاً إضافيًا. ثالثًا، إذا كان المكون يُعرض نادرًا، فإن الحمل الإضافي لتخزين ذاكرة التخزين المؤقت غير مبرر.
وفقًا لـ Kent C. Dodds, 2023، قبل إضافة useMemo، يجب قياس المشكلة باستخدام React DevTools Profiler أو مراقب الأداء المدمج في React Native. إذا كانت إعادة عرض المكون تستغرق أقل من 1 مللي ثانية، فإن التخزين المؤقت غير ضروري.
useMemo و useCallback — خطافا تخزين مؤقت من React غالبًا ما يتم الخلط بينهما. الفرق يكمن في ما يحتفظان به: useMemo يُرجع نتيجة تنفيذ دالة (أي قيمة)، بينما useCallback يُرجع الدالة نفسها. كلاهما يقبل مصفوفة تبعيات، لكن الصياغة تختلف.
| الخاصية | useMemo | useCallback |
|---|---|---|
| يُرجع | نتيجة تنفيذ الدالة | مرجعًا للدالة |
| الوسيط الأول | دالة تُرجع قيمة | دالة ليتم تخزينها |
| الاستخدام النموذجي | تخزين الحسابات مؤقتًا | استدعاءات ثابتة للمكونات الفرعية |
| التكافؤ | useMemo(() => fn, deps) | useCallback(fn, deps) === useMemo(() => fn, deps) |
عمليًا، useCallback هو سكر نحوي فوق useMemo للحالات التي تحتاج فيها إلى تخزين دالة. في React Native، يتم استخدام كلا الخطافين بنفس التواتر: useMemo — للبيانات (نص منسق، أنماط محسوبة)، useCallback — لمعالجات الأحداث (onPress, onChangeText).
لنلقِ نظرة على سيناريوهين واقعيين لاستخدام useMemo في تطبيقات React Native. الأول هو تصفية قائمة جهات الاتصال حسب استعلام البحث، والثاني هو حساب الأنماط الديناميكية بناءً على حجم الشاشة.
في هذا المثال، يمنع useMemo التصفية المتكررة لقائمة كبيرة من جهات الاتصال في كل إعادة عرض إذا لم يتغير لا استعلام البحث ولا البيانات المصدر.
const ContactList = ({ contacts, searchQuery }) => {
const filteredContacts = useMemo(() => {
if (!searchQuery.trim()) return contacts;
const query = searchQuery.toLowerCase();
return contacts.filter((c) =>
c.name.toLowerCase().includes(query) ||
c.phone.includes(query)
);
}, [contacts, searchQuery]);
return (
<FlatList
data={filteredContacts}
renderItem={renderContact}
keyExtractor={(item) => item.id}
/>
);
};
بدون useMemo، كانت التصفية ستُنفذ في كل إعادة عرض، بما في ذلك تلك الناتجة عن الرسوم المتحركة أو تغييرات الحالة في أجزاء أخرى من المكون. مع useMemo، تعمل التصفية فقط عندما تتغير contacts أو searchQuery.
في React Native، تعتمد الأنماط غالبًا على أبعاد الشاشة أو props المكون. يسمح useMemo بحساب كائن الأنماط هذا مرة واحدة وإعادة استخدامه حتى تتغير التبعيات.
const Card = ({ title, isSelected }) => {
const cardStyle = useMemo(() => ({
backgroundColor: isSelected ? '#e3f2fd' : '#ffffff',
borderWidth: isSelected ? 2 : 1,
padding: 16,
}), [isSelected]);
return (
<View style={cardStyle}>
<Text>{title}</Text>
</View>
);
};
يضمن هذا النهج أن كائن cardStyle يبقى مستقرًا (نفس المرجع) حتى يتغير isSelected. إذا كان Card مغلفًا بـ React.memo، يمنع هذا النمط إعادة العرض غير الضرورية للعناصر الفرعية.
الأسئلة الشائعة
useMemo يُرجع قيمة مخزنة مؤقتًا — نتيجة تنفيذ الدالة المُمررة كوسيط أول. في عمليات إعادة العرض اللاحقة، يُرجع React القيمة المخبأة إذا لم تتغير التبعيات.
useMemo يُرجع نتيجة تنفيذ الدالة (أي قيمة: رقم، كائن، JSX)، بينما useCallback يُرجع الدالة نفسها. لاحظ أن useCallback(fn, deps) يعادل useMemo(() => fn, deps).
useMemo عديم الفائدة في الحسابات التافهة (الجمع، الدمج)، إعادة العرض النادرة، التبعيات التي تتغير في كل إعادة عرض، والأنواع البدائية. قس الأداء دائمًا قبل إضافة useMemo.
نعم، إذا استُخدم بدون داعٍ. يخزن React القيم السابقة في الذاكرة ويقارن التبعيات في كل إعادة عرض. للحسابات البسيطة، يتجاوز الحمل الإضافي لهذه العمليات فائدة التخزين المؤقت.
استخدم React DevTools Profiler لقياس أوقات عرض المكونات. في React Native، يتوفر مراقب الأداء أيضًا عبر قائمة المطور. إذا كانت إعادة العرض تستغرق أقل من 1 مللي ثانية — useMemo غير ضروري.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا