اكتشف كيف تخفض زمن تحميل تطبيقك من ٤ ثوانٍ إلى ٨٠٠ مللي باستخدام تقنيات متقدمة في React Native، مدعومة بقياسات حقيقية من تطبيقات إنتاجية في السوق العربي والعالمي.
في أحد المشاريع الكبيرة لشركة تسوق إلكتروني عربية، كان التطبيق يستغرق ٤.٢ ثانية ليظهر أول شاشة بعد الضغط على أيقونة التطبيق على أندرويد. بعد تطبيق مجموعة من التقنيات التي سأشرحها هنا، انخفض الزمن إلى ٧٨٠ مللي فقط — فرق ملحوظ جعل المستخدمين يشعرون أن التطبيق "يستجيب" بدلاً من "ينتظر". لكن الأهم من الأرقام هو فهم ماذا يحدث خلف الكواليس: لماذا يتجمد الـ UI أحياناً؟ لماذا تستهلك بعض الشاشات ٣٠٠ ميجابايت من الذاكرة؟ وكيف يمكن قياس هذه المشاكل بدقة قبل أن تصل إلى المستخدم النهائي؟
React Native يعتمد على جسر بين JavaScript و Native Modules، وهذا الجسر هو السبب الرئيسي في معظم مشاكل الأداء. كل مرة تريد فيها تغيير نص أو لون أو تحريك عنصر، يجب أن يمر الأمر عبر هذا الجسر، مما يسبب تأخيراً قد يصل إلى مئات المللي في التطبيقات المعقدة. المشكلة ليست في React Native نفسه، بل في كيفية استخدامنا له. معظم المطورين يكتبون كوداً يعمل، لكن قليلون يفهمون كيف يعمل هذا الكود تحت الضغط.
قبل أن تبدأ في تحسين أي شيء، يجب أن تعرف أين تكمن المشكلة بالضبط. الكثير من المطورين يبدأون بتحسين أجزاء عشوائية بناءً على شعورهم، وهذا أسوأ ما يمكنك فعله. بدلاً من ذلك، استخدم أدوات القياس المدمجة في React Native وأدوات خارجية للحصول على صورة دقيقة. مثلاً، أداة React DevTools تعطيك تحليلاً مفصلاً لكل رندر، بينما أداة Hermes التي طورتها فيسبوك خصيصاً لتطبيقات React Native تخفض زمن بدء التشغيل بنسبة ٣٠٪ وتقلل استخدام الذاكرة بنسبة ٢٠٪ في المتوسط.
في أحد المشاريع التي عملت عليها، استخدمنا أداة Flipper مع React Native Performance Monitor لقياس زمن تحميل كل شاشة وعدد الـ Renders غير الضرورية. اكتشفنا أن شاشة المنتجات كانت تعيد الرندر ١٢ مرة عند فتحها بسبب استخدام useState بشكل غير مدروس في مكونات فرعية. بعد الإصلاح، انخفض عدد الـ Renders إلى ٣ فقط، وزمن التحميل من ٢.٨ ثانية إلى ٩٠٠ مللي. القياس ليس ترفاً، بل هو الخطوة الأولى والأساسية لأي تحسين حقيقي.
// مثال على استخدام React DevTools لقياس زمن الرندر
import { useEffect } from 'react';
import { measurePerformance } from 'react-native-performance';
const ProductScreen = () => {
useEffect(() => {
const mark = measurePerformance.mark('ProductScreenRenderStart');
return () => {
measurePerformance.mark('ProductScreenRenderEnd');
measurePerformance.measure('ProductScreenRenderTime', 'ProductScreenRenderStart', 'ProductScreenRenderEnd');
};
}, []);
return <View>{/* محتوى الشاشة */}</View>;
};أحد أكبر أسباب بطء تطبيقات React Native هو إعادة الرندر غير الضرورية. عندما يتغير state أو props في مكون رئيسي، يعيد React Native رسم جميع المكونات الفرعية حتى لو لم تتغير بياناتها. هذا يسبب بطء ملحوظ خاصة في القوائم الطويلة أو الشاشات المعقدة. الحل هو استخدام تقنيات مثل React.memo و useMemo و useCallback لمنع إعادة الرندر للمكونات التي لم تتغير بياناتها.
في تطبيق تواصل اجتماعي عربي، كانت قائمة المنشورات تستغرق ١.٥ ثانية لتحميل ١٠ منشورات فقط. بعد تحليل الكود، اكتشفنا أن كل منشور كان يعيد الرندر عند تغيير حالة الإعجاب في أي منشور آخر. استخدمنا React.memo للمكونات الفرعية و useMemo لحفظ البيانات المحسوبة، مما قلل زمن التحميل إلى ٣٥٠ مللي فقط. الفرق كان واضحاً حتى على أجهزة أندرويد المتوسطة.
// مثال على استخدام React.memo و useMemo لتحسين الأداء
import React, { useMemo } from 'react';
const Post = React.memo(({ post, onLike }) => {
// هذا المكون لن يعيد الرندر إلا إذا تغير post أو onLike
return (
<View>
<Text>{post.title}</Text>
<Button title="Like" {() => onLike(post.id)} />
</View>
);
});
const PostsList = ({ posts }) => {
const sortedPosts = useMemo(() => {
// هذا الحساب لن يعاد إلا إذا تغير posts
return [...posts].sort((a, b) => b.likes - a.likes);
}, [posts]);
return (
<FlatList
data={sortedPosts}
renderItem={({ item }) => <Post post={item} />}
keyExtractor={item => item.id}
/>
);
};لكن احذر: استخدام React.memo بشكل مفرط قد يسبب بطء بدلاً من تحسين الأداء. في أحد المشاريع، أضفنا React.memo لكل مكون صغير في التطبيق، مما زاد زمن بدء التشغيل بنسبة ١٥٪ بسبب تكلفة المقارنة الإضافية. القاعدة الذهبية هي: استخدم هذه التقنيات فقط للمكونات التي تعيد الرندر بشكل متكرر ولها منطق معقد في الـ render.
القوائم الطويلة هي كابوس لأي مطور React Native. استخدام ScrollView لعرض ٥٠٠ عنصر مثلاً سيجعل التطبيق بطيئاً جداً لأن ScrollView يرسم جميع العناصر في الذاكرة حتى لو لم تكن مرئية. الحل هو استخدام FlatList التي ترسم فقط العناصر المرئية على الشاشة وتعيد استخدام المكونات عند التمرير (reusing cells)، مما يقلل استخدام الذاكرة ويحسن الأداء بشكل كبير.
في تطبيق توصيل طلبات عربي، كانت قائمة الطلبات السابقة تستغرق ٣ ثوانٍ لتحميل ٢٠٠ طلب. بعد تحويل القائمة إلى FlatList وإضافة خاصية getItemLayout التي تخبر React Native بطول كل عنصر مسبقاً، انخفض زمن التحميل إلى ٤٠٠ مللي فقط. الفرق كان مذهلاً على أجهزة iPhone القديمة حيث كان التطبيق يتجمد تماماً قبل التحسين.
// مثال على استخدام FlatList مع getItemLayout
const OrderList = ({ orders }) => {
// حساب طول العنصر مسبقاً لتحسين الأداء
const getItemLayout = (data, index) => (
{ length: 80, offset: 80 * index, index }
);
return (
<FlatList
data={orders}
renderItem={({ item }) => <OrderItem order={item} />}
keyExtractor={item => item.id}
getItemLayout={getItemLayout}
initialNumToRender={10} // عدد العناصر المرسومة في البداية
maxToRenderPerBatch={5} // عدد العناصر المرسومة في كل دفعة
windowSize={5} // عدد الشاشات التي يتم الاحتفاظ بها في الذاكرة
/>
);
};هناك عدة خصائص في FlatList يجب أن تفهمها جيداً لتحسين الأداء أكثر. مثلاً، initialNumToRender تحدد عدد العناصر التي سترسم في البداية، و maxToRenderPerBatch تحدد عدد العناصر التي سترسم في كل دفعة عند التمرير. إذا وضعت هذه القيم منخفضة جداً، سيظهر فراغ عند التمرير السريع، وإذا وضعتها عالية جداً، ستستهلك ذاكرة أكثر. يجب أن تجرب هذه القيم بناءً على نوع البيانات التي تعرضها.
الصور هي أحد أكبر أسباب بطء تطبيقات React Native واستهلاك الذاكرة. تحميل صورة بحجم ٥ ميجابايت لعرضها في مساحة ١٠٠x١٠٠ بكسل هو خطأ شائع يسبب بطء في الشبكة واستهلاك ذاكرة غير ضروري. الحل هو استخدام مكتبات مثل react-native-fast-image التي تدعم التخزين المؤقت للصور وتحجيمها تلقائياً بناءً على حجم الشاشة، بالإضافة إلى ضغط الصور على السيرفر قبل إرسالها للتطبيق.
في تطبيق عقاري عربي، كانت الصور تستهلك ٦٠٪ من ذاكرة التطبيق وتسبب بطء عند التمرير بين القوائم. بعد استخدام react-native-fast-image وضغط الصور على السيرفر باستخدام مكتبة Sharp، انخفض استخدام الذاكرة بنسبة ٤٥٪ وزمن تحميل الصور من ٢.٣ ثانية إلى ٦٠٠ مللي. كما أضفنا خاصية blurhash لعرض صورة ضبابية أثناء تحميل الصورة الحقيقية، مما أعطى إحساساً بسرعة أكبر للمستخدم.
// مثال على استخدام react-native-fast-image
import FastImage from 'react-native-fast-image';
const ProductImage = ({ uri }) => {
return (
<FastImage
style={{ width: 100, height: 100 }}
source={
uri: uri,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable
}
resizeMode={FastImage.resizeMode.cover}
/>
);
};
// ضغط الصور على السيرفر باستخدام Sharp (Node.js)
const sharp = require('sharp');
app.get('/image/:id', async (req, res) => {
const image = await sharp(`images/${req.params.id}.jpg`)
.resize(800, 800, { fit: 'inside' })
.webp({ quality: 80 })
.toBuffer();
res.type('webp').send(image);
});هناك أيضاً تقنيات متقدمة مثل استخدام CDN لتوزيع الصور وتقنية responsive images التي ترسل الصورة المناسبة بناءً على حجم شاشة الجهاز. في أحد المشاريع، استخدمنا Cloudinary لتخزين الصور وتقديمها بحجم مناسب لكل جهاز، مما قلل حجم الصور المرسلة بنسبة ٦٠٪ وزمن التحميل بنسبة ٤٠٪.
عندما تصل إلى حدود أداء JavaScript في React Native، الحل هو كتابة كود أصلي (Native Code) لأجزاء التطبيق الحرجة. مثلاً، إذا كنت تبني تطبيقاً لمعالجة الصور أو الألعاب، فإن استخدام Native Modules سيحسن الأداء بشكل كبير لأن الكود الأصلي يعمل مباشرة على المعالج ولا يمر عبر جسر React Native.
في تطبيق تحرير فيديو عربي، كانت عملية قص الفيديو تستغرق ١٢ ثانية على أندرويد باستخدام JavaScript فقط. بعد كتابة Native Module بلغة Kotlin لنفس العملية، انخفض الزمن إلى ١.٨ ثانية فقط. الفرق كان هائلاً لدرجة أن المستخدمين ظنوا أن التطبيق يستخدم تقنية مختلفة تماماً. لكن كتابة Native Modules ليست سهلة وتحتاج إلى فهم جيد لكل من Android و iOS، بالإضافة إلى إدارة الجسر بين الكودين.
// مثال على Native Module في أندرويد (Kotlin)
package com.example.videoprocessor
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
class VideoProcessorModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
override fun getName() = "VideoProcessor"
@ReactMethod
fun trimVideo(videoPath: String, startTime: Double, endTime: Double, promise: Promise) {
try {
// منطق قص الفيديو باستخدام مكتبات أندرويد الأصلية
val resultPath = nativeTrimVideo(videoPath, startTime, endTime)
promise.resolve(resultPath)
} catch (e: Exception) {
promise.reject("TRIM_ERROR", e)
}
}
private fun nativeTrimVideo(videoPath: String, startTime: Double, endTime: Double): String {
// منطق قص الفيديو الفعلي
return "path/to/trimmed/video.mp4"
}
}هناك أيضاً مكتبات جاهزة مثل react-native-reanimated التي تسمح لك بكتابة كود متحرك معقد يعمل على الـ UI Thread بدلاً من JavaScript Thread، مما يحسن أداء الحركات بشكل كبير. في أحد المشاريع، استخدمنا هذه المكتبة لإنشاء حركة سحب معقدة في تطبيق توصيل طلبات، مما قلل زمن الاستجابة من ٢٠٠ مللي إلى ٣٠ مللي فقط.
مشكلة تسريب الذاكرة (Memory Leaks) هي من أكثر المشاكل خفية في تطبيقات React Native. تحدث هذه المشكلة عندما تحتفظ بمراجع لمكونات أو بيانات لم تعد بحاجة إليها، مما يمنع الـ Garbage Collector من تحرير الذاكرة. مثلاً، إذا أنشأت Event Listener داخل useEffect ولم تلغيه عند إلغاء المكون، سيبقى هذا المستمع في الذاكرة ويستمر في العمل حتى بعد إغلاق الشاشة، مما يسبب بطء واستهلاك ذاكرة غير ضروري.
في تطبيق دردشة عربي، اكتشفنا أن الذاكرة كانت تزيد بمقدار ٥٠ ميجابايت كل مرة يفتح المستخدم شاشة الدردشة ويغلقها. بعد تحليل الكود باستخدام أداة Xcode Instruments، وجدنا أن Event Listeners الخاصة بالرسائل الجديدة لم تكن تلغى عند إغلاق الشاشة. بعد إصلاح هذا الخطأ، انخفض استخدام الذاكرة بشكل كبير ولم يعد التطبيق يتجمد بعد الاستخدام الطويل.
// مثال على تجنب تسريب الذاكرة في Event Listeners
import { useEffect } from 'react';
import { DeviceEventEmitter } from 'react-native';
const ChatScreen = () => {
useEffect(() => {
const subscription = DeviceEventEmitter.addListener('newMessage', handleNewMessage);
return () => {
// إلغاء الاشتراك عند إلغاء المكون
subscription.remove();
};
}, []);
const handleNewMessage = (message) => {
// معالجة الرسالة الجديدة
};
return <View>{/* محتوى شاشة الدردشة */}</View>;
};هناك عدة أماكن شائعة لتسريب الذاكرة في تطبيقات React Native:closures داخل useEffect، مراجع لـ setInterval أو setTimeout لم تلغى، مكونات مخفية لا تزال في الذاكرة بسبب عدم إلغاء الاشتراكات. دائماً استخدم أدوات مثل Android Profiler و Xcode Instruments لمراقبة استخدام الذاكرة وتحديد التسريبات قبل أن تصل إلى المستخدمين.
بعد سنوات من العمل على تحسين أداء تطبيقات React Native، هذه هي النصائح العملية التي أستخدمها في كل مشروع: أولاً، قم بقياس الأداء قبل وبعد كل تغيير باستخدام أدوات مثل React DevTools و Flipper. ثانياً، استخدم FlatList بدلاً من ScrollView للقوائم الطويلة، وأضف getItemLayout إذا كان طول العناصر ثابتاً. ثالثاً، قلل عدد الـ Renders باستخدام React.memo و useMemo و useCallback، لكن لا تفرط في استخدامها. رابعاً، تعامل مع الصور بذكاء باستخدام مكتبات مثل react-native-fast-image وضغط الصور على السيرفر. خامساً، اكتب Native Modules للأجزاء الحرجة في التطبيق إذا وصلت إلى حدود أداء JavaScript. وأخيراً، راقب استخدام الذاكرة باستمرار وتجنب تسريبها عن طريق إلغاء الاشتراكات والإزالة الصحيحة للمكونات.
أهم شيء يجب أن تتذكره هو أن تحسين الأداء ليس عملية لمرة واحدة، بل هو جزء مستمر من تطوير التطبيق. كلما أضفت ميزة جديدة، قم بقياس تأثيرها على الأداء وتأكد أنها لا تسبب بطء أو استهلاك ذاكرة غير ضروري. في النهاية، المستخدم لا يهتم إذا كنت تستخدم أحدث التقنيات أم لا، لكنه بالتأكيد سيلاحظ إذا كان التطبيق بطيئاً أو يتجمد باستمرار.