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

No es necesario que envíes más código del necesario a los usuarios, así que divide los paquetes para asegurarte de que nunca suceda.

El método React.lazy facilita la división del código de una aplicación de React a nivel de componente mediante 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 utilidades y bibliotecas de terceros. Si no se intenta cargar diferentes partes de una aplicación solo cuando sean 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 separados de JavaScript con muy poco trabajo. Luego, puedes encargarte de los estados de carga cuando los vinculas 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, especialmente en dispositivos y conexiones de red débiles. Esta es la razón por la que la división de código y la carga diferida son sumamente útiles.

Sin embargo, siempre habrá una leve demora que los usuarios deban 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. 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 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, haz lo siguiente:

  • Para obtener una vista previa del sitio, presiona Ver app. Luego, presiona Pantalla completa pantalla completa.
  • Presiona "Control + Mayús + J" (o bien "Comando + Opción + J" en Mac) para abrir Herramientas para desarrolladores.
  • Haga clic en la pestaña Red.
  • Haz clic en el menú desplegable Regulació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.

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 la descarga de un archivo chunk.js

Suspende varios componentes

Otra función de Suspense es que te permite suspender la carga de varios componentes, incluso si todos son de carga 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 se terminen de recuperar todos los componentes, el usuario podrá verlos todos al mismo tiempo.

Puedes ver esto con la siguiente incorporación:

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

Cómo controlar las fallas de carga

Suspense te permite mostrar un estado de carga temporal mientras las solicitudes de red se realizan de forma interna. Pero ¿qué pasa si esas solicitudes de red fallan por alguna razón? Es posible que estés sin conexión o que tu aplicación web esté intentando cargar de forma diferida una URL con versión que esté desactualizada y ya no esté disponible después de una reimplementació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 errores. 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 del ciclo de vida static getDerivedStateFromError() o componentDidCatch().

Para detectar y controlar las fallas de carga diferida, puedes unir el componente Suspense con uno superior que sirva como límite de error. Dentro del método render() del límite de errores, puedes renderizar los elementos secundarios tal como están si no hay errores, 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 estás seguro de dónde comenzar a aplicar la división de código a tu aplicación de React, sigue estos pasos:

  1. Comienza en el 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 reacción, 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 rendericen en determinadas interacciones del usuario (como hacer clic en un botón). Dividir estos componentes minimizará las cargas útiles de JavaScript.
  3. Considera dividir todo lo que esté fuera de la pantalla y que no sea fundamental para el usuario.