viernes, 21 de julio de 2017

Componentes React Eficientes: Una Guía para Optimizar el Desempeño de React


Desde su presentación inicial, React ha cambiado la forma de pensar de desarrolladores front-end al construir aplicaciones web. Con DOM virtual, React hace las actualizaciones UI (Interfaz de Usuario) tan eficientes como nunca, haciendo al mismo tiempo a tu aplicación web más precisa. Pero, ¿por qué las aplicaciones web con React con un buen tamaño todavía tienen un desempeño pobre?
Bueno, la clave está en cómo usas React.
Una biblioteca front-end moderna como React no hace más rápida tu aplicación mágicamente. Se requiere que el desarrollador entienda cómo funciona React y cómo los componentes viven a través de las diferentes fases del ciclo de vida del componente.
Con React puedes ganar muchas de las mejoras que ofrece al medir y optimizar cómo y cuándo regresan tus componentes. Y React proporciona sólo las herramientas y funciones necesarias para hacer esto más fácil.
Acelera tu aplicación React al optimizar tu proceso renderización-diff de los componentes.
En este tutorial React, aprenderás cómo puedes medir el desempeño de tus componentes de React y optimizarlos para construir una aplicación web con React de mayor y mejor desempeño. De igual modo, aprenderás cómo un par de las mejores prácticas JavaScript también ayudan a que tu aplicación web con React presente una experiencia de usuario sin inconvenientes.

¿Cómo funciona React?

Antes de entrar a las técnicas de optimización, necesitamos entender mejor cómo funciona React.
En el corazón de React se encuentra la sintaxis JSX y la habilidad de React de construir y comparar DOM virtuales. Desde su lanzamiento, React ha influenciado muchas otras bibliotecas front-end. Bibliotecas tales como Vue.js también se apoyan en la idea de DOM virtuales.
Aquí se ve cómo funciona React:
Cada aplicación React comienza con un componente de raíz y se compone de muchos componentes en una formación de árbol. Los componentes en React son “funciones” que dejan la UI basada en la data (apoyo y estado) que recibe.
Esto lo podemos simbolizar como F.
UI = F(data)
Los usuarios interactúan con la UI y ocasionan un cambio en la data. Ya sea que la interacción sea dar clic a un botón, tocar una imagen, arrastrar ítems de una lista, peticiones AJAX que llaman API, etc., todas esas interacciones solo cambian la data. Nunca causan que la UI cambie directamente.
Aquí, la data es todo lo que define el estado de la aplicación web y no solo lo que has almacenado en tu base de datos. Hasta trozos de los estados front-end (ej. Qué ventana se selecciona en el momento o si una casilla se marcó en el momento) son parte de esta data.
Cuando hay un cambio en esta data, React usa las funciones de componente para volver a dejar la UI, pero solo virtualmente:
UI1 = F(data1)
UI2 = F(data2)
React calcula las diferencias entre la UI actual y la nueva UI al aplicar un algoritmo comparativo en las dos versiones de su DOM virtual.
Changes = Diff(UI1, UI2)
React luego procede a aplicar solo los cambios a la UI real en el buscador.
Cuando la data asociada al componente cambia, React determina si en realidad se necesita una actualización DOM. Esto le permite a React poder evitar operaciones de manipulación DOM costosas en el buscador, como creación de nodos DOM y acceder a algunos de estos ya existentes más de lo necesario.
Este cálculo de diferenciación repetido y el dejar componentes, puede ser una las fuentes primarias de los problemas de desempeño de React en cualquier aplicación con React. Construir una aplicación React en la que la diferenciación de algoritmos no se logra reconciliar efectivamente, causando así que toda la aplicación sea dejada repetidamente, puede resultar en una experiencia frustrante y lenta.

¿Dónde comenzar la optimización?

¿Pero qué estamos optimizando exactamente?
Bueno, durante el proceso inicial de dejar atrás, React construye un árbol DOM como éste:
Un DOM virtual de componentes React
Dado que una parte de la data cambia, lo que queremos que haga React es dejar de nuevo sólo los componentes que se ven afectados directamente por el cambio (y posiblemente obviar el proceso de diff para el resto de los componentes):
React renderiza un numero de componentes
Sin embargo, lo que React termina haciendo es:
React mal gastando recursos al renderizar todos los componentes
En la imagen de arriba, todos los nodos amarillos son renderizados y diferenciados (diff), dando como resultado una pérdida de tiempo/recursos de computación. Aquí es donde pondremos nuestros esfuerzos de optimización, principalmente. Configurar cada componente para que sólo renderice-diferencie (diff) cuando sea necesario, nos permitirá recuperar estos ciclos CPU perdidos.
Los desarrolladores de la biblioteca React tomaron esto en consideración y proporcionaron un enganche para nosotros poder hacer exactamente eso: una función que nos permite decirle a React cuando está bien obviar la renderización de un componente.

Medir Primero

Como lo dice Rob Pike de manera elegante como una de sus reglas de programación:
Medir. No ajustes la velocidad hasta que hayas medido y aun en ese momento no lo hagas, a menos que una parte del código opaque el resto.
No optimices un código que pienses que puede bajar la velocidad de tu aplicación. En vez de esto, deja que las herramientas de medida de desempeño de React te guíen en el camino.
React tiene una fuerte herramienta para esto. Al usar la biblioteca react-addons-perf puedes obtener un resumen del desempeño general de tu aplicación.
El uso es muy simple:
Import Perf from 'react-addons-perf'
Perf.start();
// use the app
Perf.stop();
Perf.printWasted();
Esto imprimirá una tabla con la cantidad de componentes de tiempo perdido al renderizar.
Tabla de componentes perdiendo tiempo al renderizar
La biblioteca proporciona otras funciones que te permiten imprimir diferentes aspectos del tiempo perdido por separado (ej., al usar las funciones printInclusive() o printExclusive()), o hasta imprimir las operaciones de manipulación DOM (usando la función printOperations()).

Tomando la Comparativa del Mercado Más Allá

Si eres una persona visual, entonces react-perf-tool es exactamente lo que necesitas.
react-perf-tool está basada en la biblioteca react-addons-perf. Te da una manera más visual de depurar el desempeño de tu aplicación con React. Ésta usa la biblioteca subyacente para obtener medidas y luego las visualiza como gráficas.
Una visualización de componentes perdiendo tiempo en renderización
Muy a menudo, ésta es una manera mucho más conveniente de notar las trancas. La puedes usar fácilmente al agregarla como componente a tu aplicación.

¿React Debería Actualizar el Componente?

Por defecto, React se ejecutará, renderizará el DOM virtual y comparará la diferencia para cada componente en el árbol para cada cambio en sus apoyos y estados. Pero eso no es racional, obviamente.
Mientras tu aplicación crece, intentar re-renderizar y comparar el DOM virtual completo en cada acción, eventualmente disminuirá la velocidad.
React proporciona una manera sencilla para que el desarrollador indique si un componente necesita ser re-renderizado. Aquí es donde el método shouldComponentUpdate entra en juego.
function shouldComponentUpdate(nextProps, nextState) {
   return true;
}
Cuando esta función da un resultado verdadero para cualquier componente, permite que se active el proceso de renderización-diferenciación.
Esto te da una manera sencilla de controlar el proceso de renderización-diferenciación. Cuando necesites prevenir que un componente sea re-renderizado por completo, simplemente es un resultado falso de la función. Dentro de la función, puedes comparar el set de apoyos y estado actual, al igual que el siguiente para determinar si es necesario re-renderizar:
function shouldComponentUpdate(nextProps, nextState) {
   return nextProps.id !== this.props.id;
}

Usar un React.PureComponent

Para facilitar y automatizar un poco esta técnica de optimización, React proporciona lo que se conoce como componente “puro”. Un React.PureComponent es exactamente como un React.Component que implementa una función shouldComponentUpdate() con una comparación vacía de apoyo y estado.
Un React.PureComponent es más o menos un equivalente a esto:
class MyComponent extends React.Component {
   shouldComponentUpdate(nextProps, nextState) {
       return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState);
   }
   …
}
Ya que sólo realiza una comparación vacía, tal vez sólo te parezca útil cuando:
  • Tus apoyos y estados contienen data primitiva.
  • Tus apoyos y estados tienen data compleja, pero sabes cuándo llamar forceUpdate() para actualizar tu componente.

Hacer la Data Inmutable

¿Qué tal si pudieras usar React.PureComponent y seguir teniendo una manera eficiente de ver cuando cualquier apoyo o estado complejo ha cambiado automáticamente? Aquí es donde las estructuras de data inmutable nos hacen la vida más fácil.
La idea detrás del uso de las estructuras de data inmutable es simple. Cuando un objeto que contiene cambios de data complejos, en vez de hacer los cambios en ese objeto, crea una copia de ese objeto con los cambios. Esto hace el detectar los cambios en la data tan simple como comparar la referencia de los dos objetos.
Puedes usar Object.assign o _.extend (desde Underscore.js o Lodash):
const newValue2 = Object.assign({}, oldValue);
const newValue2 = _.extend({}, oldValue);
Aun mejor, puedes usar una biblioteca que proporciona estructuras de data inmutable:
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1.equals(map2) === true);
var map3 = map1.set('b', 50);
assert(map1.equals(map3) === false);
Aquí, Immutable.Map es proporcionada por la biblioteca Immutable.js.
Cada vez que un mapa se actualiza con su método set, un nuevo mapa se regresa sólo si la operación set cambió el valor subyacente. De otro modo, el mismo mapa se regresa.
Puedes aprender más sobre el uso de estructuras de data inmutable aquí.

Más Técnicas de Optimización de Aplicación React

Usando la Construcción de Producción

Cuando se desarrolla una aplicación con React, se presentan advertencias y mensajes de errores muy útiles. Estos hacen la identificación de bugs y problemas durante el desarrollo muy fácil. Pero también cuestan parte del desempeño.
Si observas el código fuente de React, verás muchas marcas de if (process.env.NODE_ENV != 'production'). Estos trozos de código que React está ejecutando en tu ambiente de desarrollo no es algo que el usuario necesite. Para ambientes de producción, todo este código innecesario se puede descartar.
Si impulsaste tu proyecto usando create-react-app, entonces puedes ejecutar npm run build para producir la construcción de producción sin este código extra. Si estás usando Webpack directamente, puedes ejecutar webpack -p (el cual es el equivalente de webpack --optimize-minimize --define process.env.NODE_ENV="'production'".

Atar Funciones Desde Temprano

Es muy común ver funciones atadas al contexto del componente dentro de la función renderizada. Esto es, a menudo, el caso, cuando usamos estas funciones para manejar eventos de componentes en temprana etapa.
// Creates a new `handleUpload` function during each render()
<TopBar onUpload={this.handleUpload.bind(this)} />
// ...as do inlined arrow functions
<TopBar onUpload={files => this.handleUpload(files)} />
Esto causará que la función render() cree una nueva función en cada renderización. Una mejor manera de hacer lo mismo es:
class App extends React.Component {
   constructor(props) {
       super(props);
       this.handleUpload = this.handleUpload.bind(this);
   }
   render() {
       …
       <TopBar onUpload={this.handleUpload} />
       …
   }
}

Usar Múltiples Archivos Chunk

Para las aplicaciones web con React de una sola página, normalmente terminamos uniendo todo nuestro código JavaScript front-end en un archivo único minimizado. Esto funciona muy bien para aplicaciones web de tamaño medio. Pero mientras la aplicación comienza a crecer, entregar este archivo JavaScript unido al buscador como tal puede ser un proceso que consume mucho tiempo.
Si estás usando Webpack para construir tu aplicación, puedes impulsar su código al separar sus capacidades para igualmente separar tu código de aplicación construida en varios “chunks” (trozos) y entregarlos al buscador en los momentos necesarios.
Hay dos tipos de separación: separación de recursos y separación de código en demanda.
Con la separación de recursos, separas contenido de recursos en varios archivos. Por ejemplo, al usar CommonsChunkPlugin, puedes extraer un código común (como todas las bibliotecas externas) a un archivo “chunk” propiamente. Al usar ExtractTextWebpackPlugin, puedes extraer todo código CSS a un archivo CSS separado.
Este tipo de separación te ayudará de dos maneras. Ayuda al buscador a almacenar aquellos recursos que cambian con menos frecuencia. También ayudará al buscador a tomar ventaja de la descarga paralela para reducir, potencialmente, el tiempo de carga.
Una característica más notable de Webpack es la separación del código en demanda. Esto puede mantener la descarga inicial pequeña al reducir el tiempo que toma cargar la aplicación. El buscador puede, consecuentemente, descargar trozos de código en demanda cuando la aplicación lo necesite.
Puedes aprender más sobre la separación de código Webpack aquí.

Activar Gzip en tu Servidor Web

El grupo de archivos JS de la aplicación React son, comúnmente, muy grandes así que para hacer que la página web cargue más rápido, podemos activar Gzip en el servidor web (Apache, Nginx, etc.)
Todos los buscadores modernos apoyan y negocian automáticamente la compresión de Gzip para las peticiones HTTP. Activar la compresión Gzip puede reducir el tamaño de la respuesta transferida en un 90%, lo cual puede reducir la cantidad de tiempo para descargar un recurso, reducir el uso de data para el cliente y mejorar el tiempo para la renderización de tus páginas.
Verifica la documentación para tu servidor web para ver cómo activar la compresión:

Usar Eslint-plugin-react

Deberías usar ESLint para casi todos los proyectos JavaScript. React no es la diferencia.
Con eslint-plugin-react, te estarás obligando a adaptarte a muchas de las reglas de programación de React, que pueden beneficiar tu código a la larga y evitar muchos problemas comunes e inconvenientes que ocurren debido a la mala escritura de códigos.

Haz a tus Aplicaciones React rápidas Nuevamente

Para aprovechar a React al máximo, necesitas impulsar sus herramientas y técnicas. El desempeño de una aplicación web de React se encuentra en la simplicidad de sus componentes. Abrumar el algoritmo de renderización y diferenciación puede hacer que tu aplicación se desempeñe pobremente de forma muy frustrante.
Antes de que puedas optimizar tu aplicación, necesitas entender cómo funcionan los componentes de React y como se renderizan en tu buscador. Los métodos del ciclo de vida de React te dan maneras de prevenir que tu componente re-renderice innecesariamente. Elimina aquellas trancas y tendrás el desempeño de la aplicación que merecen tus usuarios.
Aunque hay más formas de optimizar una aplicación web con React, perfeccionar los componentes para actualizar sólo cuando se requiere, impide una mejora en el desempeño.
Articulo publicado originalmente en Toptal.

No hay comentarios:

Publicar un comentario

El administrador se reserva el derecho de publicar los mensajes