Veinte elementos en un feed se sienten perfectos. El desplazamiento es fluido como la seda. Tu cliente está feliz. Luego lanzas a producción, llegan los datos y, de repente, te encuentras con dos mil filas. La interfaz de usuario empieza a dar tirones. El uso de memoria aumenta hasta que el sistema operativo cierra la aplicación. En su desesperación, algunos desarrolladores envuelven todo en un ScrollView y dan el trabajo por terminado. Esa decisión suele engendrar tres nuevos errores por cada uno que resuelve.

Las listas son el cuello de botella de rendimiento que define cómo los usuarios perciben tu aplicación de React Native. Si las haces bien, la aplicación se siente nativa. Si las haces mal, incluso la pantalla más hermosa se convierte en un proceso pesado. La causa raíz suele ser un desajuste entre el componente que elegiste y el trabajo que le pides a los hilos de JavaScript y de la interfaz de usuario. React Native funciona en dos vías. Tu lógica vive en el hilo de JS, mientras que el renderizado (painting) ocurre en el hilo de la interfaz de usuario nativa. Cuando renderizas una lista masiva de forma incorrecta, ambos hilos comienzan a ahogarse en cálculos de diseño (layout), re-renderizados y asignaciones de memoria. El resultado es la pérdida de fotogramas, destellos en blanco y, finalmente, un cierre inesperado (crash).

Elige la herramienta adecuada

Elegir un componente de lista debe ser una decisión arquitectónica deliberada, no un reflejo.

ScrollView es la opción más sencilla. Toma cada hijo que le entregues, monta cada uno de ellos en memoria inmediatamente y le entrega todo el montón al motor de desplazamiento nativo. Eso es exactamente lo que quieres para contenido corto y fijo, como una pantalla de configuración, un formulario de inicio de sesión o una página de detalles de producto estática con diez secciones. Es predecible y fácil de estilizar. El problema es que no tiene virtualización. Si le entregas dos mil elementos, obedientemente creará dos mil vistas nativas. No uses ScrollView para conjuntos de datos grandes o dinámicos. Piensa en él como un póster enmarcado, no como un estante de biblioteca.

FlatList es el caballo de batalla para feeds largos y uniformes. Virtualiza el contenido, lo que significa que solo monta las filas que están actualmente visibles o cerca del área de visualización (viewport). A medida que el usuario se desplaza, FlatList desmonta las celdas que salen de la pantalla y las recicla para los datos entrantes. Esto mantiene la memoria estable, independientemente de cuánto crezca tu array. Si estás construyendo una línea de tiempo social, un centro de notificaciones o cualquier colección de tarjetas similares con desplazamiento continuo, FlatList es la opción correcta por defecto.

SectionList es FlatList con sentido de la organización. Úsala cuando tus datos lleguen en grupos, como una libreta de direcciones ordenada alfabéticamente, un registro de entrenamiento dividido por fecha o una lista de facturas organizada por mes. Renderiza encabezados de sección pegajosos (sticky) y gestiona la lógica de agrupación por ti. Bajo el capó, utiliza el mismo motor de virtualización que FlatList, por lo que obtienes los mismos beneficios de memoria con la estructura añadida de particiones tituladas.

FlashList entra en juego cuando necesitas exprimir hasta el último fotograma del dispositivo. Construida sobre el ecosistema de RecyclerListView, recicla vistas de forma más agresiva que FlatList y tiene como objetivo mantener sesenta fotogramas por segundo incluso en hardware de gama media. Si estás construyendo una interfaz de chat de alto volumen, un catálogo de productos con una velocidad de desplazamiento rápida o cualquier pantalla donde la fluidez sea una ventaja competitiva, FlashList vale la pena la dependencia adicional. No es necesaria para cada pantalla, pero para los feeds que definen la experiencia principal, la diferencia de rendimiento es notable.

Asesinos comunes del rendimiento

Hay tres sospechosos habituales cuando una lista empieza a ralentizarse.

Montar demasiados árboles de React a la vez es el fallo más dramático. Cuando cada fila es un árbol de componentes complejo, el renderizado inicial puede bloquear el hilo de JS lo suficiente como para producir una pantalla blanca vacía o un primer renderizado (paint) retrasado y feo. El usuario abre la aplicación y espera. Incluso después de la carga inicial, las filas pesadas hacen que la inicialización del scroll sea lenta porque los primeros fotogramas se consumen en el trabajo de configuración.

Demasiado trabajo por fotograma se manifiesta como tirones durante el desplazamiento. Tienes un presupuesto de aproximadamente dieciséis milisegundos por fotograma para mantener las animaciones fluidas. Si un componente de fila ejecuta cálculos costosos, analiza fechas sobre la marcha o realiza comparaciones profundas de objetos dentro del render, agotarás ese presupuesto. El hilo de la interfaz de usuario pierde fotogramas y el usuario siente una sacudida.

El uso excesivo de memoria es el asesino silencioso. Cada vista nativa cuesta RAM. Añade imágenes grandes no optimizadas, sombras en cada tarjeta o elementos táctiles (touchables) anidados, y la huella de memoria se multiplica. En iOS, el sistema puede terminar tu aplicación sin previo aviso. En Android, el usuario observa cómo aumenta el retraso hasta que la aplicación se vuelve inutilizable.

Lista de verificación de optimización

Los pequeños hábitos estratégicos separan una lista que simplemente funciona de una que vuela.

Usa claves estables. Pasa siempre un identificador real de tu conjunto de datos a la prop key. Nunca uses el índice del array. Si tu lista se reordena, se filtra o se le añaden elementos, una clave basada en el índice engaña a React, haciendo que empareje los datos incorrectos con el componente reciclado equivocado. Ese error provoca desmontajes innecesarios, desajustes de estado y re-renders en cascada. Un ID adecuado le indica a React exactamente qué fila se movió a dónde.

Memoriza las filas. Envuelve tu componente de fila en React.memo para que solo se vuelva a renderizar cuando sus props cambien realmente. Sin esta protección, cualquier actualización del estado del padre puede activar un ciclo de renderizado en cada fila visible, incluso si sus datos son idénticos. En una lista larga con mucho movimiento, esos ciclos desperdiciados se acumulan rápidamente.

Mantén renderItem estable. Evita definir una nueva función directamente dentro de la prop renderItem en cada renderizado del padre. Una función de flecha en línea como renderItem={({ item }) => <Row data={item} />} crea una nueva referencia cada vez que el padre se actualiza. FlatList detecta una prop cambiada y recicla la fila innecesariamente. Define la función de renderizado fuera del componente o memorízala con useCallback para que la referencia se mantenga estable.

Usa getItemLayout siempre que sea posible. Si tus filas tienen una altura fija o predecible, dile a FlatList exactamente cuál es. Esta prop permite que la lista se salte las costosas llamadas de medición nativa. En lugar de medir cada celda después del montaje, la lista calcula la posición matemáticamente. La diferencia es especialmente notable en listas con cientos o miles de elementos, donde el exceso de eventos onLayout puede dejar al hilo de JS de rodillas.

Optimiza las imágenes agresivamente. Las imágenes sin dimensiones definidas son veneno para las listas. Establece siempre un ancho y alto explícitos para que la capa nativa reserve el espacio antes de que la imagen se decodifique. Para imágenes remotas, utiliza una librería de caché como Expo Image o una equivalente que gestione el almacenamiento en caché de memoria, la persistencia en disco y la optimización de formato. El componente Image por defecto de React Native funciona para prototipos, pero los feeds de producción necesitan más control sobre la memoria y los estados de carga.

Evita anidar contenedores de scroll. Nunca coloques una FlatList vertical dentro de un ScrollView vertical. El ScrollView padre captura todos los eventos de scroll y altera la capacidad de la FlatList hija para medir su viewport. La virtualización falla porque FlatList ya no sabe qué filas deberían ser visibles. El resultado es que cada fila se monta de todos modos, anulando el propósito de la virtualización. Si necesitas un encabezado sobre una lista, usa la prop ListHeaderComponent de la propia FlatList. Si necesitas un comportamiento sticky complejo, usa una SectionList o FlashList con la configuración de encabezado adecuada.

La regla de oro

Si el contenido es pequeño y finito, deja que ScrollView se encargue. Si el contenido crece con datos generados por el usuario o paginación remota, usa una lista virtualizada. Cuando la lista es la pieza central de la aplicación y los usuarios harán scroll durante minutos, recurre a FlashList.

Aquí hay una última verdad que es fácil de olvidar. Las filas aburridas hacen scroll rápido. Cuanto más ligero sea tu componente de fila, más fluida será tu lista. Elimina las navegaciones anidadas, los cálculos pesados y las animaciones innecesarias de la fila individual. Mantén el marcado plano, la lógica ligera y las imágenes con el tamaño adecuado. Una lista vive o muere por el peso acumulado de lo que renderiza. Haz que cada fila sea barata, y la lista se sentirá cara en el mejor sentido posible.