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

Nunca es necesario que envíes 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 del código de una aplicación de React a nivel del 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 por intentar cargar diferentes partes de una aplicación solo cuando son necesarias, se enviará un solo paquete grande de JavaScript a los 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 ocuparte de los estados de carga cuando lo combines con el componente Suspense.

Suspense

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

Sin embargo, siempre habrá un pequeño retraso que los usuarios deberán experimentar cuando se recupere un componente dividido por código a través de la red, por lo que es importante mostrar un estado de carga útil. Usar 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, momento en el que 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 breve período. Los componentes más grandes pueden tardar mucho más en cargarse, especialmente en conexiones de red débiles.

Para demostrar mejor cómo funciona, sigue estos pasos:

  • Para obtener una vista previa del sitio, presiona Ver app y, luego, Pantalla completa pantalla completa.
  • Presiona "Control + Mayúsculas + J" (o "Comando + Opción + J" en Mac) para abrir DevTools.
  • 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ápida.
  • Haz clic en el botón Click Me en la app.

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

El panel de red de Herramientas para desarrolladores muestra un archivo chunk.js que se está descargando

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 extremadamente útil de retrasar la renderización de varios componentes y mostrar solo un estado de carga. Una vez que todos los componentes terminan de recuperarse, el usuario puede verlos todos mostrados al mismo tiempo.

Puedes verlo con la siguiente incorporación:

Sin esto, es fácil encontrarse con el problema de la carga escalonada, o que diferentes partes de una IU se carguen una tras otra, cada una con su propio indicador de carga. Esto puede hacer que la experiencia del usuario sea 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é sucede si esas solicitudes de red fallan por algún motivo? Es posible que no tengas conexión o que tu app web esté intentando 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 correctamente estos tipos de errores 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 cualquiera de los métodos de ciclo de vida static getDerivedStateFromError() o componentDidCatch() (o ambos).

Para detectar y controlar las fallas de carga diferida, puedes unir tu componente Suspense con un componente principal que funcione como 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 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 todo lo demás que esté fuera de la pantalla y no sea crítico para el usuario.