الدرس 24 · الوحدة 11: تحسين أداء التطبيقات وتقليل البطء
تقليل إعادة الرسم: memo والتخزين المؤقت
أدوات تمنع العمل الزائد وتوفّر طاقة المعالج. ⏱ نحو 40 دقيقة.
بعد هذا الدرس ستستطيع أن:
- تمنع إعادة الرسم غير الضروريّة بـ
React.memo. - تحفظ الحسابات الثقيلة بـ
useMemo. - تعرف دور
useCallbackمع مكوّناتmemo.
١إعادة الرسم غير الضروريّة
حين تتغيّر حالة مكوّنٍ أب، يُعاد رسم كل أبنائه — حتى من لم تتغيّر بياناته. على الهاتف هذا يهدر المعالج والبطارية. ثلاث أدواتٍ تعالج ذلك:
| الأداة | تمنع | متى |
|---|---|---|
React.memo | إعادة رسم مكوّنٍ لم تتغيّر خصائصه | مكوّن ابنٍ ثقيل |
useMemo | إعادة حسابٍ ثقيلٍ كل رسم | عمليّة مكلفة |
useCallback | إنشاء دالّةٍ جديدة كل رسم | دالّة تُمرَّر لمكوّنٍ memo |
٢React.memo: ابنٌ لا يُعاد رسمه
نغلّف المكوّن بـ React.memo فلا يُعاد رسمه إلّا إذا تغيّرت خصائصه. افتح Console: العدّاد يتغيّر لكن البطاقة الثابتة لا تُطبع ثانيةً:
import { useState, memo } from 'react';import { View, Text, Pressable, StyleSheet } from 'react-native'; const InfoCard = memo(function InfoCard({ title }) { console.log('رُسِمت البطاقة:', title); // يُطبع مرّةً فقط return <Text style={styles.card}>{title}</Text>;}); export default function App() { const [n, setN] = useState(0); return ( <View style={styles.box}> <Text style={styles.n}>العدّاد: {n}</Text> <InfoCard title="بطاقة ثابتة" /> <Pressable style={styles.btn} onPress={() => setN(n + 1)}> <Text style={styles.bt}>زيادة</Text> </Pressable> </View> );} const styles = StyleSheet.create({ box: { padding: 24, alignItems: 'center', gap: 10 }, n: { fontSize: 24, fontWeight: 'bold' }, card: { backgroundColor: '#FFF3E0', padding: 12, borderRadius: 10, color: '#E8820C', fontWeight: 'bold' }, btn: { backgroundColor: '#E8820C', padding: 10, borderRadius: 8 }, bt: { color: 'white' },});
اضغط «زيادة» وراقب Console: «رُسِمت البطاقة» تظهر مرّةً فقط رغم تغيّر العدّاد — memo منع إعادة الرسم.
٣useMemo: حسابٌ مكلفٌ مرّةً
useMemo يحفظ نتيجة حسابٍ ثقيل، فلا يُعاد إلّا إذا تغيّرت مدخلاته:
import { useState, useMemo } from 'react';import { View, Text, TextInput, StyleSheet } from 'react-native'; function heavySum(n) { let s = 0; for (let i = 0; i < n * 1000; i++) s += i; // عمليّة مكلفة return s;} export default function App() { const [n, setN] = useState('5'); const [other, setOther] = useState(''); const num = Number(n) || 0; const result = useMemo(() => heavySum(num), [num]); // يُحسب فقط عند تغيّر num return ( <View style={{ padding: 20, gap: 10 }}> <TextInput style={styles.in} keyboardType="numeric" value={n} onChangeText={setN} placeholder="رقم" /> <Text style={styles.r}>نتيجة الحساب الثقيل: {result}</Text> <TextInput style={styles.in} value={other} onChangeText={setOther} placeholder="اكتب هنا (لا يعيد الحساب)" /> </View> );} const styles = StyleSheet.create({ in: { borderWidth: 1, borderColor: '#ccc', borderRadius: 8, padding: 8 }, r: { fontWeight: 'bold', color: '#2E7D32' },});
الكتابة في الحقل الثاني لا تعيد الحساب الثقيل، لأن مدخلاته (num) لم تتغيّر. هذا جوهر useMemo.
توقّع وتحقّق
React.memo يمنع:
useMemo يحفظ:
useCallback يثبّت:
متى نحسّن؟
الخلاصة
React.memoيمنع إعادة رسم ابنٍ لم تتغيّر خصائصه.useMemoيحفظ نتيجة حسابٍ ثقيل حتى تتغيّر مدخلاته.useCallbackيثبّت الدوالّ المُمرَّرة لمكوّناتmemo— وكلّها بعد القياس.
تحدٍّ برمجي: بطاقة memo
متقدمامنع إعادة رسم مكوّنٍ ثابت بـ memo.
المطلوب:
- عرّف مكوّن
Labelمغلَّفًا بـmemoيستقبل prop اسمهtext. - يعرض
<Text testID="label">فيه النصّ. - استخدمه في App بـ
text="ثابت"بجانب عدّادٍ متغيّر.
👀 اعرض الناتج المطلوب (وجرّبه)
✓ التحقق الآلي من حلّك:
- الكود يعمل بلا أخطاء في Console
- النصّ «ثابت»
- استُخدم memo
- يستقبل prop text
💡 تلميح 1
const Label = memo(function Label({{ text }}) {{ … }}).💡 تلميح 2
memo يمنع إعادة رسم Label عند تغيّر العدّاد.
حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
import { useState, memo } from 'react';import { View, Text, Pressable, StyleSheet } from 'react-native'; const Label = memo(function Label({ text }) { return <Text testID="label" style={styles.l}>{text}</Text>;}); export default function App() { const [n, setN] = useState(0); return ( <View style={{ padding: 20, gap: 10, alignItems: 'center' }}> <Text style={styles.n}>{n}</Text> <Label text="ثابت" /> <Pressable style={styles.btn} onPress={() => setN(n + 1)}> <Text style={styles.bt}>زيادة</Text> </Pressable> </View> );} const styles = StyleSheet.create({ n: { fontSize: 28, fontWeight: 'bold', color: '#E8820C' }, l: { backgroundColor: '#FFF3E0', padding: 8, borderRadius: 8 }, btn: { backgroundColor: '#E8820C', padding: 10, borderRadius: 8 }, bt: { color: 'white' },});