Twenty items in a feed feels perfect. The scroll is butter. Your client is happy. Then you ship to production, the data arrives, and suddenly you are staring at two thousand rows. The UI starts to hitch. Memory creeps up until the OS pulls the plug. In desperation, some developers wrap everything in a ScrollView and call it a day. That decision usually births three new bugs for every one it solves.

Lists are the performance bottleneck that defines how users perceive your React Native app. Get them right and the app feels native. Get them wrong and even the most beautiful screen becomes a slog. The root cause is usually a mismatch between the component you chose and the work you are asking the JavaScript and UI threads to perform. React Native runs on two tracks. Your logic lives on the JS thread while painting happens on the native UI thread. When you render a massive list incorrectly, both threads start drowning in layout calculations, re-renders, and memory allocations. The result is dropped frames, blank flashes, and eventually a crash.

Pick the Right Tool

Choosing a list component should be a deliberate architectural decision, not a reflex.

ScrollView is the simplest option. It takes every child you give it, mounts every single one into memory immediately, and hands the whole pile to the native scroll engine. That is exactly what you want for short, fixed content such as a settings screen, a login form, or a static product detail page with ten sections. It is predictable and easy to style. The catch is that there is no virtualization. If you feed it two thousand items, it will obediently create two thousand native views. Do not use ScrollView for large or dynamic data sets. Think of it as a framed poster, not a library shelf.

FlatList is the workhorse for long, uniform feeds. It virtualizes content, which means it only mounts rows that are currently visible or near the viewport. As the user scrolls, FlatList unmounts cells that leave the screen and recycles them for incoming data. This keeps memory flat regardless of how large your array grows. If you are building a social timeline, a notification center, or any continuously scrolling collection of similar cards, FlatList is the default correct choice.

SectionList is FlatList with a sense of organization. Use it when your data arrives in grouped buckets, such as an address book sorted alphabetically, a workout log split by date, or an invoice list organized by month. It renders sticky section headers and handles the grouping logic for you. Under the hood it uses the same virtualization engine as FlatList, so you get identical memory benefits with the added structure of titled partitions.

FlashList steps in when you need to squeeze every last frame out of the device. Built on top of the RecyclerListView ecosystem, it recycles views more aggressively than FlatList and aims to maintain sixty frames per second even on mid-range hardware. If you are building a high-volume chat interface, a product catalog with rapid scroll velocity, or any screen where smoothness is a competitive advantage, FlashList is worth the added dependency. It is not necessary for every screen, but for feeds that define the core experience, the performance delta is noticeable.

Common Performance Killers

There are three usual suspects when a list starts to drag.

Mounting too many React trees at once is the most dramatic failure. When every row is a complex component tree, the initial render can block the JS thread long enough to produce a blank white screen or an ugly delayed first paint. The user opens the app and waits. Even after the initial load, heavy rows make scroll initialization sluggish because the first few frames are consumed by setup work.

Too much work per frame shows up as stuttering during scroll. You have a budget of roughly sixteen milliseconds per frame to keep animations smooth. If a row component runs expensive calculations, parses dates on the fly, or performs deep object comparisons inside render, you blow that budget. The UI thread drops frames and the user feels a jolt.

Excessive memory use is the silent killer. Every native view costs RAM. Add large unoptimized images, drop shadows on every card, or nested touchables and the footprint multiplies. On iOS the system may terminate your app without warning. On Android the user watches the lag build until the app feels unusable.

Optimization Checklist

العادات الاستراتيجية الصغيرة هي ما يميز القائمة التي تعمل فحسب عن تلك التي تعمل بأداء فائق.

استخدم مفاتيح (keys) مستقرة. قم دائمًا بتمرير معرف حقيقي من مجموعة بياناتك إلى خاصية key. لا تستخدم فهرس المصفوفة (array index) أبدًا. إذا تمت إعادة ترتيب القائمة، أو تصفيتها، أو إضافة عناصر إليها، فإن المفتاح المعتمد على الفهرس يخدع React ويجعله يربط البيانات الخاطئة بالمكون المعاد تدويره بشكل خاطئ. هذا الخطأ يتسبب في عمليات unmount غير ضرورية، وعدم تطابق في الحالة (state mismatches)، وإعادة رندرة (re-renders) متتالية. المعرف (ID) الصحيح يخبر React بالضبط أي صف قد انتقل إلى أين.

استخدم الـ Memoization للصفوف. قم بتغليف مكون الصف الخاص بك في React.memo بحيث لا يتم إعادة رندرتها إلا عندما تتغير خصائصها (props) فعليًا. بدون هذا الإجراء الوقائي، يمكن لأي تحديث في حالة المكون الأب (parent state) أن يؤدي إلى عملية رندرة لكل صف مرئي، حتى لو كانت بياناتها متطابقة. في القوائم الطويلة التي تتغير باستمرار، تتراكم هذه الدورات الضائعة بسرعة.

حافظ على استقرار renderItem. تجنب تعريف دالة جديدة مباشرة داخل خاصية renderItem عند كل عملية رندرة للمكون الأب. دالة السهم المضمنة (inline arrow function) مثل renderItem={({ item }) => <Row data={item} />} تنشئ مرجعًا (reference) جديدًا في كل مرة يتم فيها تحديث المكون الأب. ترى FlatList أن الخاصية قد تغيرت فتقوم بإعادة تدوير الصف دون داعٍ. قم بتعريف دالة الرندرة خارج المكون أو استخدم useCallback لضمان بقاء المرجع مستقرًا.

استخدم getItemLayout كلما أمكن ذلك. إذا كانت صفوفك ذات ارتفاع ثابت أو يمكن التنبؤ به، فأخبر FlatList بذلك تحديدًا. تسمح هذه الخاصية للقائمة بتجاوز استدعاءات القياس الأصلية (native measurement) المكلفة. فبدلاً من قياس كل خلية بعد عملية الـ mount، تقوم القائمة بحساب الموقع رياضيًا. يظهر الفرق بوضوح خاصة في القوائم التي تحتوي على مئات أو آلاف العناصر، حيث يمكن لضجيج onLayout أن ينهك خيط الـ JS (JS thread).

قم بتحسين الصور بقوة. الصور غير المحدودة الأبعاد هي سمّ للقوائم. قم دائمًا بتحديد عرض وارتفاع صريحين حتى تحجز الطبقة الأصلية (native layer) مساحة قبل فك تشفير الصورة. بالنسبة للصور البعيدة (remote images)، استخدم مكتبة تخزين مؤقت (caching library) مثل Expo Image أو ما يعادلها مما يتعامل مع التخزين المؤقت في الذاكرة، والاستمرارية على القرص، وتحسين التنسيق. مكون Image الافتراضي في React Native يعمل للنماذج الأولية، لكن تطبيقات الإنتاج تحتاج إلى تحكم أكبر في الذاكرة وحالات التحميل.

تجنب تداخل حاويات التمرير. لا تضع أبدًا FlatList عمودية داخل ScrollView عمودي. يقوم ScrollView الأب بالتقاط جميع أحداث التمرير ويعطل قدرة FlatList التابعة على قياس منطقة العرض (viewport) الخاصة بها. تتعطل عملية الـ Virtualization لأن FlatList لم تعد تعرف أي الصفوف يجب أن تكون مرئية. والنتيجة هي أن كل صف يتم عمل mount له على أي حال، مما يبطل الغرض الكامل من الـ virtualization. إذا كنت بحاجة إلى رأس (header) فوق القائمة، فاستخدم خاصية ListHeaderComponent الخاصة بـ FlatList. وإذا كنت بحاجة إلى سلوك "التثبيت" (sticky behavior) المعقد، فاستخدم SectionList أو FlashList مع تكوين الرأس المناسب.

القاعدة الذهبية

إذا كان المحتوى صغيرًا ومحدودًا، فاترك الأمر لـ ScrollView. أما إذا كان المحتوى ينمو ببيانات من إنشاء المستخدم أو عبر الترقيم (pagination) عن بُعد، فاستخدم قائمة افتراضية (virtualized list). وعندما تكون القائمة هي العنصر الأساسي في التطبيق ويقوم المستخدمون بالتمرير لدقائق في المرة الواحدة، فاستخدم FlashList.

إليك حقيقة أخيرة من السهل نسيانها: الصفوف "المملة" تمرر بسرعة. كلما كان مكون الصف الخاص بك أخف، كانت قائمتك أكثر سلاسة. جرد الصف الفردي من التنقلات المتداخلة، والحسابات الثقيلة، والرسوم المتحركة غير الضرورية. حافظ على بنية markup مسطحة، ومنطق بسيط، وصور محددة الحجم. تعيش القائمة أو تموت بناءً على الوزن التراكمي لما تقوم برندرتة. اجعل كل صف "رخيصًا" (من حيث استهلاك الموارد)، وستشعر أن القائمة "فاخرة" بأفضل طريقة ممكنة.