ข้อมูล 20 รายการในฟีดดูเหมือนจะสมบูรณ์แบบ การเลื่อนดูนั้นลื่นไหลเหมือนเนย ลูกค้าของคุณก็แฮปปี้ แต่พอคุณส่งงานขึ้น production ข้อมูลจริงมาถึง และจู่ๆ คุณก็ต้องเผชิญกับข้อมูลถึงสองพันแถว UI เริ่มกระตุก หน่วยความจำ (Memory) ค่อยๆ พุ่งสูงขึ้นจนกระทั่ง OS สั่งปิดแอปฯ ด้วยความสิ้นหวัง นักพัฒนาบางคนจึงเลือกที่จะห่อทุกอย่างไว้ใน ScrollView แล้วก็จบงานไป แต่การตัดสินใจแบบนั้นมักจะสร้างบั๊กใหม่ขึ้นมาสามตัวต่อทุกๆ หนึ่งบั๊กที่มันแก้ได้

List คือคอขวดด้านประสิทธิภาพ (performance bottleneck) ที่กำหนดว่าผู้ใช้จะรู้สึกอย่างไรกับแอป React Native ของคุณ ถ้าคุณจัดการมันได้ดี แอปจะให้ความรู้สึกเหมือนแอป Native แต่ถ้าทำพลาด แม้แต่หน้าจอที่สวยที่สุดก็อาจจะกลายเป็นเรื่องน่าเบื่อ สาเหตุหลักมักเกิดจากความไม่สอดคล้องกันระหว่างคอมโพเนนต์ที่คุณเลือกกับงานที่คุณสั่งให้ JavaScript และ UI threads ทำ React Native ทำงานบนสองเส้นทาง (tracks) โดย Logic ของคุณจะอยู่บน JS thread ในขณะที่การวาดภาพ (painting) จะเกิดขึ้นบน native UI thread เมื่อคุณเรนเดอร์รายการขนาดใหญ่ไม่ถูกต้อง ทั้งสอง thread จะเริ่มจมกองงานจากการคำนวณ layout, การ re-render และการจัดสรรหน่วยความจำ (memory allocations) ผลลัพธ์ที่ได้คือเฟรมตก (dropped frames), หน้าจอขาววูบวาบ และในที่สุดแอปก็ค้างหรือเด้งออก (crash)

เลือกเครื่องมือที่ใช่

การเลือกคอมโพเนนต์สำหรับ List ควรเป็นการตัดสินใจทางสถาปัตยกรรมที่ผ่านการคิดมาอย่างดี ไม่ใช่แค่ทำไปตามสัญชาตญาณ

ScrollView เป็นตัวเลือกที่ง่ายที่สุด มันจะรับทุก child ที่คุณใส่เข้าไป แล้วทำการ mount ทุกตัวลงในหน่วยความจำทันที จากนั้นจึงส่งข้อมูลทั้งหมดให้แก่ native scroll engine นั่นคือสิ่งที่คุณต้องการสำหรับเนื้อหาที่สั้นและคงที่ เช่น หน้าการตั้งค่า (settings screen), ฟอร์มล็อกอิน หรือหน้ารายละเอียดสินค้าแบบ static ที่มีประมาณสิบส่วน มันคาดเดาได้ง่ายและปรับแต่งสไตล์ได้สะดวก แต่ข้อเสียคือมันไม่มีระบบ virtualization หากคุณใส่ข้อมูลเข้าไปสองพันรายการ มันก็จะสร้าง native views ขึ้นมาสองพันตัวอย่างว่าง่าย อย่าใช้ ScrollView สำหรับชุดข้อมูลขนาดใหญ่หรือข้อมูลที่มีการเปลี่ยนแปลงตลอดเวลา ให้คิดซะว่ามันคือโปสเตอร์ในกรอบรูป ไม่ใช่ชั้นวางหนังสือในห้องสมุด

FlatList คือม้างานสำหรับฟีดที่ยาวและมีรูปแบบเหมือนกัน มันทำ virtualization ให้กับเนื้อหา ซึ่งหมายความว่ามันจะ mount เฉพาะแถวที่กำลังมองเห็นหรืออยู่ใกล้กับ viewport เท่านั้น เมื่อผู้ใช้เลื่อนหน้าจอ FlatList จะ unmount เซลล์ที่หลุดจากหน้าจอออกไป และนำไปรีไซเคิล (recycle) สำหรับข้อมูลใหม่ที่กำลังจะเข้ามา วิธีนี้จะช่วยรักษาการใช้หน่วยความจำให้คงที่ ไม่ว่า array ของคุณจะใหญ่ขึ้นแค่ไหนก็ตาม หากคุณกำลังสร้าง social timeline, ศูนย์แจ้งเตือน (notification center) หรือคอลเลกชันการ์ดที่คล้ายกันซึ่งต้องเลื่อนดูอย่างต่อเนื่อง FlatList คือตัวเลือกที่ถูกต้องโดยพื้นฐาน

SectionList คือ FlatList ที่มีการจัดระเบียบข้อมูล ใช้เมื่อข้อมูลของคุณมาเป็นกลุ่มๆ เช่น สมุดรายชื่อที่เรียงตามตัวอักษร, บันทึกการออกกำลังกายที่แบ่งตามวันที่ หรือรายการใบแจ้งหนี้ที่จัดกลุ่มตามเดือน มันจะเรนเดอร์ sticky section headers และจัดการตรรกะการจัดกลุ่มให้คุณ เบื้องหลังการทำงานมันใช้ virtualization engine ตัวเดียวกับ FlatList ดังนั้นคุณจึงได้รับประโยชน์ด้านหน่วยความจำแบบเดียวกัน แต่ได้โครงสร้างการแบ่งส่วนที่มีหัวข้อเพิ่มเข้ามา

FlashList จะเข้ามามีบทบาทเมื่อคุณต้องการรีดประสิทธิภาพทุกเฟรมออกมาจากอุปกรณ์ มันถูกสร้างขึ้นบนระบบนิเวศของ RecyclerListView โดยจะรีไซเคิล views ได้ดุดันกว่า FlatList และมีเป้าหมายเพื่อรักษาความเร็วที่ 60 เฟรมต่อวินาที (60 FPS) แม้จะอยู่บนฮาร์ดแวร์ระดับกลางก็ตาม หากคุณกำลังสร้างอินเทอร์เฟซแชทที่มีปริมาณข้อความสูง, แคตตาล็อกสินค้าที่ต้องเลื่อนดูอย่างรวดเร็ว หรือหน้าจอใดๆ ที่ความลื่นไหลคือความได้เปรียบในการแข่งขัน FlashList ก็คุ้มค่าที่จะเพิ่ม dependency เข้ามา มันไม่จำเป็นสำหรับทุกหน้าจอ แต่สำหรับฟีดที่เป็นหัวใจสำคัญของประสบการณ์ผู้ใช้ ความแตกต่างด้านประสิทธิภาพนั้นเห็นได้อย่างชัดเจน

ตัวการทำลายประสิทธิภาพที่พบบ่อย

มีผู้ต้องสงสัยสามรายเมื่อ List เริ่มทำงานช้าลง

การ mount React trees จำนวนมากเกินไปในคราวเดียวคือความล้มเหลวที่รุนแรงที่สุด เมื่อทุกแถวเป็น component tree ที่ซับซ้อน การเรนเดอร์ครั้งแรกอาจบล็อก JS thread นานพอที่จะทำให้เกิดหน้าจอขาวว่างเปล่า หรือการวาดภาพครั้งแรก (first paint) ที่ล่าช้าจนดูไม่สวยงาม ผู้ใช้เปิดแอปแล้วต้องนั่งรอ แม้จะผ่านการโหลดครั้งแรกไปแล้ว แต่แถวที่หนักเกินไปจะทำให้การเริ่มต้นเลื่อนหน้าจอ (scroll initialization) เชื่องช้า เพราะเฟรมแรกๆ ถูกใช้ไปกับการทำงานเตรียมความพร้อม (setup work)

การทำงานต่อเฟรมที่มากเกินไปจะปรากฏในรูปแบบของการกระตุกระหว่างการเลื่อน คุณมีงบประมาณเวลาประมาณ 16 มิลลิวินาทีต่อเฟรมเพื่อให้แอนิเมชันลื่นไหล หากคอมโพเนนต์ในแถวมีการคำนวณที่กินทรัพยากรสูง, มีการ parse วันที่แบบสดๆ (on the fly) หรือมีการเปรียบเทียบออบเจกต์เชิงลึก (deep object comparisons) ภายใน render คุณจะใช้เวลาเกินงบที่ตั้งไว้ ส่งผลให้ UI thread ทำเฟรมตก และผู้ใช้จะรู้สึกถึงอาการกระตุก

การใช้หน่วยความจำที่มากเกินไปคือเพชฌฆาตเงียบ ทุกๆ native view มีต้นทุนเป็น RAM หากคุณเพิ่มรูปภาพขนาดใหญ่ที่ไม่ได้ปรับแต่ง (unoptimized), ใส่ drop shadows ในทุกการ์ด หรือใช้ touchables แบบซ้อนกัน (nested) ปริมาณการใช้หน่วยความจำก็จะทวีคูณขึ้น ใน iOS ระบบอาจสั่งปิดแอปของคุณโดยไม่มีการแจ้งเตือน ส่วนใน Android ผู้ใช้จะเห็นอาการแล็กที่สะสมมากขึ้นเรื่อยๆ จนกระทั่งแอปใช้งานไม่ได้

Optimization Checklist

Small strategic habits separate a list that merely works from one that flies.

Use stable keys. Always pass a real identifier from your data set to the key prop. Never use the array index. If your list reorders, filters, or appends items, an index-based key tricks React into pairing the wrong data with the wrong recycled component. That mistake triggers unnecessary unmounts, state mismatches, and cascading re-renders. A proper ID tells React exactly which row moved where.

Memoize rows. Wrap your row component in React.memo so that it only re-renders when its props actually change. Without this guard, any parent state update can trigger a render pass across every visible row, even if their data is identical. In a long list that churns, those wasted cycles add up fast.

Keep renderItem stable. Avoid defining a new function directly inside the renderItem prop on every parent render. An inline arrow function like renderItem={({ item }) => <Row data={item} />} creates a new reference each time the parent updates. FlatList sees a changed prop and recycles the row unnecessarily. Define the render function outside the component or memoize it with useCallback so the reference stays stable.

Use getItemLayout whenever possible. If your rows have a fixed or predictable height, tell FlatList exactly what it is. This prop allows the list to skip expensive native measurement calls. Instead of measuring each cell after mount, the list calculates position mathematically. The difference is especially sharp on lists with hundreds or thousands of items, where onLayout chatter can bring the JS thread to its knees.

Optimize images aggressively. Unbounded images are list poison. Always set explicit width and height so the native layer reserves space before the image decodes. For remote images, use a caching library such as Expo Image or an equivalent that handles memory caching, disk persistence, and format optimization. The default React Native Image component works for prototypes, but production feeds need more control over memory and loading states.

Avoid nesting scroll containers. Never place a vertical FlatList inside a vertical ScrollView. The parent ScrollView captures all scroll events and disrupts the child FlatList's ability to measure its viewport. Virtualization breaks because FlatList no longer knows which rows should be visible. The result is that every row mounts anyway, defeating the entire purpose of virtualization. If you need a header above a list, use FlatList's own ListHeaderComponent prop. If you need complex sticky behavior, use a SectionList or FlashList with the appropriate header configuration.

The Golden Rule

If the content is small and finite, let ScrollView handle it. If the content grows with user-generated data or remote pagination, use a virtualized list. When the list is the centerpiece of the app and users will scroll for minutes at a time, reach for FlashList.

Here is one last truth that is easy to forget. Boring rows scroll fast. The lighter your row component, the smoother your list. Strip away nested navigations, heavy computations, and gratuitous animations from the individual row. Keep the markup flat, the logic thin, and the images sized. A list lives or dies by the cumulative weight of what it renders. Make each row cheap, and the list will feel expensive in the best possible way.