División de código con React.lazy y Suspense

Nunca necesitas enviar más código del necesario a tus usuarios, así que divide tus paquetes para asegurarte de que esto nunca suceda.

El método React.lazy facilita la división de código de una aplicación de React a nivel de componente con importaciones dinámicas.

import React, { lazy } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

¿Por qué es útil?

Por lo general, una aplicación de React grande consta de muchos componentes, métodos de utilidad y bibliotecas de terceros. Si no se hace un esfuerzo para intentar cargar diferentes partes de una aplicación solo cuando se necesitan, se enviará un único paquete grande de JavaScript a tus usuarios en cuanto carguen la primera página. Esto puede afectar significativamente el rendimiento de la página.

La función React.lazy proporciona una forma integrada de separar los componentes de una aplicación en fragmentos independientes de JavaScript con muy poco trabajo. Luego, puedes encargarte de los estados de carga cuando lo acoples con el componente Suspense.

Suspense

El problema de enviar una carga útil de JavaScript grande a los usuarios es el tiempo que tardaría la página en cargarse por completo, en especial en dispositivos y conexiones de red más débiles. Por eso, la división de código y la carga diferida son muy útiles.

Sin embargo, siempre habrá una pequeña demora que los usuarios tendrán que experimentar cuando se recupera un componente de división de código a través de la red, por lo que es importante mostrar un estado de carga útil. El uso de React.lazy con el componente Suspense ayuda a resolver este problema.

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense acepta un componente fallback que te permite mostrar cualquier componente de React como un estado de carga. En el siguiente ejemplo, se muestra cómo funciona. El avatar solo se renderiza cuando se hace clic en el botón, donde se realiza una solicitud para recuperar el código necesario para el AvatarComponent suspendido. Mientras tanto, se muestra el componente de carga de resguardo.

Aquí, el código que compone AvatarComponent es pequeño, por lo que el ícono giratorio de carga solo se muestra durante un período breve. Los componentes más grandes pueden tardar mucho más en cargarse, en especial en conexiones de red débiles.

Para demostrar mejor cómo funciona esto, consulta los siguientes ejemplos:

  • Para obtener una vista previa del sitio, presiona View App y, luego, Fullscreen pantalla completa.
  • Presiona "Control + Mayúsculas + J" (o "Comando + Opción + J" en Mac) para abrir las Herramientas para desarrolladores.
  • Haga clic en la pestaña Red.
  • Haz clic en el menú desplegable Limitación, que está configurado como Sin limitación de forma predeterminada. Selecciona 3G rápido.
  • Haz clic en el botón Click Me de la app.

Ahora, el indicador de carga se mostrará durante más tiempo. Observa cómo todo el código que compone la AvatarComponent se recupera como un fragmento separado.

Panel de red de Herramientas para desarrolladores que muestra la descarga de un archivo chunk.js

Cómo suspender varios componentes

Otra característica de Suspense es que te permite suspender la carga de varios componentes, incluso si todos se cargan de forma diferida.

Por ejemplo:

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

Esta es una forma muy útil de retrasar la renderización de varios componentes y, al mismo tiempo, mostrar un solo estado de carga. Una vez que todos los componentes terminan de recuperarse, el usuario puede verlos todos mostrados al mismo tiempo.

Puedes ver esto con la siguiente incorporación:

Sin esto, es fácil encontrarse con el problema de la carga escalonada o con diferentes partes de una IU que se cargan una tras otra, cada una con su propio indicador de carga. Esto puede hacer que la experiencia del usuario se sienta más discordante.

Cómo controlar los errores de carga

Suspense te permite mostrar un estado de carga temporal mientras se realizan solicitudes de red en segundo plano. ¿Pero qué pasa si esas solicitudes de red fallan por algún motivo? Es posible que no tengas conexión o que tu app web intente cargar de forma diferida una URL con versión que está desactualizada y ya no está disponible después de una nueva implementación del servidor.

React tiene un patrón estándar para controlar de forma correcta estos tipos de fallas de carga: usar un límite de error. Como se describe en la documentación, cualquier componente de React puede servir como límite de error si implementa uno (o ambos) de los métodos de ciclo de vida static getDerivedStateFromError() o componentDidCatch().

Para detectar y controlar las fallas de carga diferida, puedes unir tu componente Suspense con un componente superior que funcione como un límite de error. Dentro del método render() del límite de error, puedes renderizar los elementos secundarios tal como están si no hay ningún error, o bien renderizar un mensaje de error personalizado si algo sale mal:

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

Conclusión

Si no sabes por dónde empezar a aplicar la división de código a tu aplicación de React, sigue estos pasos:

  1. Comienza a nivel de la ruta. Las rutas son la forma más sencilla de identificar los puntos de tu aplicación que se pueden dividir. En la documentación de React, se muestra cómo se puede usar Suspense junto con react-router.
  2. Identifica los componentes grandes de una página de tu sitio que solo se renderizan en ciertas interacciones del usuario (como hacer clic en un botón). Dividir estos componentes minimizará tus cargas útiles de JavaScript.
  3. Considera dividir cualquier otro elemento que esté fuera de la pantalla y que no sea crítico para el usuario.