Saltar al contenido principal
Versión: 0.79

Resumen de rendimiento

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Una razón convincente para usar React Native en lugar de herramientas basadas en WebView es lograr al menos 60 fotogramas por segundo y proporcionar una apariencia y comportamiento nativos a tus aplicaciones. Siempre que es posible, React Native maneja las optimizaciones automáticamente, permitiéndote centrarte en tu aplicación sin preocuparte por el rendimiento. Sin embargo, hay áreas donde aún no hemos alcanzado ese nivel, y otras donde React Native (similar a escribir código nativo directamente) no puede determinar el mejor enfoque de optimización. En esos casos, se requiere intervención manual. Nos esforzamos por ofrecer una interfaz extremadamente fluida por defecto, pero puede haber situaciones donde eso no sea posible.

Esta guía te enseñará conceptos básicos para solucionar problemas de rendimiento, además de explicar fuentes comunes de problemas y sus soluciones sugeridas.

Lo que necesitas saber sobre los fotogramas

La generación de tus abuelos llamaba a las películas "imágenes en movimiento" por una razón: el movimiento realista en video es una ilusión creada cambiando imágenes estáticas rápidamente a velocidad constante. Nos referimos a cada una de estas imágenes como fotogramas. La cantidad de fotogramas mostrados por segundo impacta directamente en la fluidez y sensación de realismo de un video (o interfaz de usuario). Los dispositivos iOS muestran al menos 60 fotogramas por segundo, lo que te da a ti y al sistema de UI como máximo 16.67ms para realizar todo el trabajo necesario para generar la imagen estática (fotograma) que el usuario verá en pantalla durante ese intervalo. Si no puedes completar el trabajo necesario para generar ese fotograma dentro del tiempo asignado, "perderás un fotograma" y la UI parecerá no responder.

Para añadir un poco de complejidad, abre el Menú de desarrollo en tu aplicación y activa Show Perf Monitor. Notarás que hay dos tasas de fotogramas diferentes.

Tasa de fotogramas JS (hilo de JavaScript)

En la mayoría de aplicaciones React Native, tu lógica de negocio se ejecuta en el hilo JavaScript. Aquí reside tu aplicación React, se realizan llamadas API, se procesan eventos táctiles, etc... Las actualizaciones de vistas nativas se agrupan y envían al lado nativo al final de cada iteración del bucle de eventos, antes del plazo del fotograma (si todo va bien). Si el hilo JavaScript no responde durante un fotograma, se considerará un fotograma perdido. Por ejemplo, si llamaras a this.setState en el componente raíz de una aplicación compleja y esto resultara en volver a renderizar subárboles de componentes computacionalmente costosos, podría tomar 200ms y resultar en perder 12 fotogramas. Cualquier animación controlada por JavaScript parecería congelarse durante ese tiempo. Si algo toma más de 100ms, el usuario lo notará.

Esto suele ocurrir durante transiciones de Navigator: al empujar una nueva ruta, el hilo JavaScript necesita renderizar todos los componentes necesarios para la escena y enviar los comandos adecuados al lado nativo para crear las vistas subyacentes. Es común que este trabajo tome varios fotogramas y cause jank porque la transición está controlada por el hilo JavaScript. A veces los componentes realizarán trabajo adicional en componentDidMount, lo que podría causar un segundo temblor en la transición.

Otro ejemplo es la respuesta a toques: si estás realizando trabajo durante múltiples fotogramas en el hilo JavaScript, podrías notar un retraso al responder a TouchableOpacity, por ejemplo. Esto ocurre porque el hilo JavaScript está ocupado y no puede procesar los eventos táctiles enviados desde el hilo principal. Como resultado, TouchableOpacity no puede reaccionar a los eventos táctiles y ordenar a la vista nativa que ajuste su opacidad.

Tasa de fotogramas de UI (hilo principal)

Muchos han notado que el rendimiento de NavigatorIOS es mejor que el de Navigator inmediatamente. La razón es que las animaciones de transición se realizan completamente en el hilo principal, por lo que no se interrumpen por pérdidas de fotogramas en el hilo JavaScript.

De manera similar, puedes desplazarte sin problemas en un ScrollView cuando el hilo JavaScript está bloqueado, porque el ScrollView reside en el hilo principal. Los eventos de desplazamiento se envían al hilo JS, pero su recepción no es necesaria para que ocurra el desplazamiento.

Fuentes comunes de problemas de rendimiento

Ejecución en modo desarrollo (dev=true)

El rendimiento del hilo de JavaScript se ve muy afectado en modo desarrollo. Esto es inevitable: se requiere mucho más trabajo en tiempo de ejecución para proporcionar advertencias y mensajes de error útiles. Siempre verifica el rendimiento en compilaciones de producción.

Uso de declaraciones console.log

Al ejecutar una aplicación empaquetada, estas declaraciones pueden crear un cuello de botella en el hilo de JavaScript. Esto incluye llamadas de bibliotecas de depuración como redux-logger, así que asegúrate de eliminarlas antes del empaquetado. También puedes usar este plugin de Babel que elimina todas las llamadas console.*. Primero instálalo con npm i babel-plugin-transform-remove-console --save-dev, luego edita el archivo .babelrc en tu directorio de proyecto así:

json
{
"env": {
"production": {
"plugins": ["transform-remove-console"]
}
}
}

Esto eliminará automáticamente todas las llamadas console.* en las versiones de producción de tu proyecto.

Se recomienda usar el plugin incluso si no hay llamadas console.* en tu proyecto. Una biblioteca de terceros también podría hacerlas.

El renderizado inicial de ListView es muy lento o el desplazamiento es deficiente en listas grandes

Utiliza los nuevos componentes FlatList o SectionList en su lugar. Además de simplificar la API, los nuevos componentes de lista tienen mejoras significativas de rendimiento, siendo la principal un uso de memoria casi constante para cualquier cantidad de filas.

Si tu FlatList se renderiza lentamente, asegúrate de haber implementado getItemLayout para optimizar la velocidad de renderizado omitiendo la medición de los elementos renderizados.

Los FPS de JS caen al volver a renderizar una vista que apenas cambia

Si usas ListView, debes proporcionar una función rowHasChanged que reduzca mucho trabajo determinando rápidamente si una fila necesita volver a renderizarse. Si usas estructuras de datos inmutables, esto solo requeriría una verificación de igualdad por referencia.

De manera similar, puedes implementar shouldComponentUpdate e indicar las condiciones exactas bajo las cuales deseas que el componente vuelva a renderizarse. Si escribes componentes puros (donde el valor de retorno de la función render depende completamente de props y estado), puedes aprovechar PureComponent para hacer esto por ti. Nuevamente, las estructuras de datos inmutables son útiles para mantener esto rápido: si tienes que hacer una comparación profunda de una gran lista de objetos, podría ser más rápido volver a renderizar todo el componente, y ciertamente requeriría menos código.

Caída de FPS en el hilo JS por exceso de trabajo simultáneo

Las "transiciones lentas en el Navigator" son la manifestación más común de este problema, pero también puede ocurrir en otros escenarios. Usar InteractionManager puede ser un buen enfoque, pero si el impacto en la experiencia de usuario es demasiado alto al retrasar tareas durante una animación, considera usar LayoutAnimation.

La API Animated actualmente calcula cada fotograma clave bajo demanda en el hilo de JavaScript a menos que configures useNativeDriver: true, mientras que LayoutAnimation aprovecha Core Animation y no se ve afectada por caídas de fotogramas en los hilos JS o principal.

Un caso donde he usado esto es al animar un modal (deslizándose desde arriba con superposición translúcida) mientras se inicializan y reciben respuestas de múltiples solicitudes de red, se renderiza el contenido del modal y se actualiza la vista origen. Consulta la guía de Animaciones para más detalles sobre LayoutAnimation.

Advertencias:

  • LayoutAnimation solo funciona para animaciones "disparar y olvidar" (estáticas). Si necesitas interrumpibilidad, usa Animated.

El movimiento de vistas (desplazamiento, traslación, rotación) reduce FPS en el hilo UI

Esto es especialmente relevante con texto sobre imágenes con fondos transparentes, o cualquier situación que requiera composición alfa en cada fotograma. Habilitar shouldRasterizeIOS o renderToHardwareTextureAndroid mejora esto significativamente.

No abuses de esto o el uso de memoria se disparará. Perfila rendimiento y memoria al usar estas propiedades. Si ya no moverás una vista, desactiva esta propiedad.

Animación del tamaño de imágenes reduce FPS en el hilo UI

En iOS, ajustar el ancho/alto de un componente Image recorta y escala la imagen original, lo que es costoso para imágenes grandes. Usa transform: [{scale}] para animar el tamaño. Ejemplo: al hacer zoom en una imagen a pantalla completa.

Mi vista TouchableX no responde bien

A veces, si realizamos una acción en el mismo fotograma en que ajustamos la opacidad o resaltado de un componente que responde al tacto, no veremos ese efecto hasta que la función onPress haya retornado. Si onPress ejecuta un setState que genera mucha carga y caída de fotogramas, esto puede ocurrir. Una solución es envolver cualquier acción dentro de tu manejador onPress en requestAnimationFrame:

tsx
handleOnPress() {
requestAnimationFrame(() => {
this.doExpensiveAction();
});
}

Transiciones lentas en el navigator

Como se mencionó, las animaciones de Navigator se controlan desde el hilo JS. En transiciones "push desde derecha", cada fotograma requiere enviar nuevos desplazamientos-X al hilo principal. Si el hilo JS se bloquea, no envía actualizaciones y la animación se entrecorta.

Una solución es descargar animaciones JS al hilo principal. Podríamos precalcular todos los desplazamientos-X al iniciar la transición y enviarlos optimizados. Así, si el hilo JS pierde fotogramas renderizando, no afectará la transición principal.

Resolver esto es un objetivo clave de React Navigation. Sus vistas usan componentes nativos y la biblioteca Animated para animaciones ≥60 FPS ejecutadas en hilos nativos.