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

Nunca debes enviar más código de lo 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 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 grande de React constará de muchos componentes, métodos de utilidad y bibliotecas de terceros. Si no se hace un esfuerzo para intentar cargar partes diferentes de una aplicación solo cuando se necesitan, 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 separados de JavaScript con muy poco trabajo. Luego, puedes encargarte de cargar los estados cuando lo combines con el componente Suspense.

Suspense

El problema de enviar una gran carga útil de JavaScript a los usuarios es el tiempo que tardaría la página en terminar de cargarse, 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 extremadamente útiles.

Sin embargo, siempre habrá una ligera demora que los usuarios deben 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, y luego 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, sigue estos pasos:

  • Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona 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 se establece en Sin limitación de forma predeterminada. Selecciona 3G rápida.
  • Haz clic en el botón Click Me en la app.

El indicador de carga ahora se mostrará por más tiempo. Observa cómo todo el código que comprende el AvatarComponent se recupera como un fragmento independiente.

Panel de red de DevTools que muestra un archivo chunk.js que se está descargando

Cómo suspender varios componentes

Otra función 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 mientras solo se muestra un solo estado de carga. Una vez que se hayan recuperado todos los componentes, el usuario podrá verlos todos a la vez.

Puedes ver esto con la siguiente incorporación:

Sin esto, es fácil encontrar 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.

Controla las fallas 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 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 con elegancia estos tipos de fallas de carga: con 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 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 un 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 los documentos 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). Si divides estos componentes, se minimizarán tus cargas útiles de JavaScript.
  3. Considera dividir todo lo que esté fuera de la pantalla y no sea fundamental para el usuario.