Skip to content
This page has been auto-translated and may contain errors.View in English

División de código

Por defecto, un bundler empaqueta toda tu app en un solo archivo JavaScript, y cada import al inicio de un archivo se suma a él. Ese paquete único crece con la app, y el navegador tiene que descargar todo antes de que algo se renderice.

En una máquina rápida con Wi-Fi fuerte puede que nunca lo notes. En un teléfono con mala conexión, una librería pesada puede bloquear toda la carga inicial, incluidas features que no necesitan nada de esa librería. El curso lo demuestra con una lista de productos que depende de un paquete fake-data enorme: con todo en un paquete, incluso el contador en la misma página se queda en blanco durante más de veinte segundos en una conexión limitada.

La división de código es la solución. Divide el paquete en pedazos, llamados chunks, y descarga un chunk solo si y cuando el usuario realmente lo necesita. Las features que el usuario nunca toca jamás se descargan.

import() dinámico

Lo primitivo debajo es JavaScript puro. Un import al inicio del archivo siempre se ejecuta. Llamar a import() como función en cambio carga un módulo bajo demanda, desde donde sea que lo llames en tu código, y retorna una promesa que se resuelve con el módulo.

jsx
// siempre cargado, parte del paquete principal
import { formatPrice } from './format'

// cargado solo cuando este handler se ejecuta
async function showChart() {
  const { renderChart } = await import('./charts')
  renderChart()
}

Los bundlers tratan cada llamada a import() como una línea de corte: el módulo importado, más las dependencias que no comparte con el paquete principal, obtienen su propio chunk, descargado por la red la primera vez que se ejecuta la llamada.

lazy y Suspense

React envuelve este primitivo en un par de herramientas. lazy toma una función que retorna un import() dinámico y te devuelve un componente que puedes renderizar como cualquier otro. La promesa del loader tiene que resolverse a un módulo cuya exportación por defecto sea el componente; otras exportaciones nombradas junto a ella están bien. Para un archivo que no controlas que solo exporta componentes nombrados, adapta la promesa en cambio: lazy(() => import('./charts').then(m => ({ default: m.RevenueChart }))).

Renderizar un componente lazy suspende: React pausa el renderizado de esa parte del árbol hasta que llega el chunk. Por sí solo eso es un error, así que envuelves el componente lazy en un límite Suspense con una prop fallback, alguna UI que mostrar mientras la descarga está en vuelo.

jsx
import { lazy, Suspense, useState } from 'react'

const ProductsList = lazy(() => import('./ProductsList'))

export default function App() {
  const [showProducts, setShowProducts] = useState(false)

  return (
    <>
      <Counter />
      <button onClick={() => setShowProducts(prev => !prev)}>
        Show products
      </button>
      {showProducts && (
        <Suspense fallback={<h2>Loading...</h2>}>
          <ProductsList />
        </Suspense>
      )}
    </>
  )
}

Ahora el paquete principal ya no contiene ProductsList ni su dependencia pesada. El contador aparece inmediatamente. Al hacer clic en el botón inicia la descarga del chunk, el fallback se muestra mientras carga, y el componente se renderiza cuando llega. Después de esa primera carga el módulo se cachea, así que ocultarlo y mostrarlo de nuevo es instantáneo.

Nota de versión

lazy y Suspense son importaciones nombradas de react. Código más antiguo, incluidas las lecciones del curso, las escribe como React.lazy y React.Suspense; son las mismas APIs.

Divide en rutas primero

Podrías buscar componentes individuales pesados, pero la línea de corte por defecto en la mayoría de apps es la ruta. Cada página es una unidad natural: un visitante en la página de inicio no tiene uso para el código del dashboard, así que haz de cada página un componente lazy y deja que el routing decida qué chunks se descargan.

jsx
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'

const Home = lazy(() => import('./pages/Home'))
const Vans = lazy(() => import('./pages/Vans'))
const Dashboard = lazy(() => import('./pages/Dashboard'))

export default function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<h2>Loading...</h2>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/vans" element={<Vans />} />
          <Route path="/host" element={<Dashboard />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  )
}

Un límite Suspense alrededor de las rutas cubre cada página. La carga inicial descarga el paquete principal más solo el chunk de la URL actual; otras páginas se descargan cuando el usuario navega a ellas. Esa amplitud tiene un costo: navegar a una página cuyo chunk aún no ha llegado reemplaza toda el área de ruta por el fallback, así que mantén el layout compartido como el encabezado y la navegación fuera del límite y se quedan en su lugar durante el cambio.

La división tiene un piso, sin embargo. Cada chunk cuesta una petición extra de red y un momento de UI fallback, así que dividir un componente minúsculo no ahorra nada que valga ese precio. Deja componentes pequeños y cualquier cosa arriba del fold que los usuarios ven inmediatamente en el paquete principal. Divide lo que es pesado, raramente usado, o detrás de una navegación.

La división sucede en tiempo de compilación. El bundler encuentra estáticamente cada llamada a import(), emite un chunk separado para ese grafo de módulos, y reescribe la llamada en una descarga para la URL de ese chunk.

lazy en sí es ligero: sostiene la promesa de tu función loader y, en el primer renderizado, la lanza, lo que es la señal de suspensión. React la atrapa, camina hasta el límite Suspense más cercano, hace commit del fallback, e intenta el renderizado de nuevo una vez que la promesa se resuelve. El loader se ejecuta una vez; después de eso el componente resuelto se cachea en el wrapper lazy y se renderiza síncronamente, y el caché del navegador hace que las revisitas sean económicas incluso entre recargas.

La ubicación del límite es una decisión de experiencia del usuario. Un límite único cerca de la raíz significa que cualquier descendiente suspendido en blanco una región grande y puede causar cambio de layout cuando el contenido llega; un límite con alcance apretado alrededor del subtree lazy mantiene el resto de la página interactiva, que es la opción que el curso elige.

La tabla de rutas es la excepción que reconcilia las dos reglas: un límite amplio allí se gana su mantenimiento porque una navegación reemplaza esa región completa de todas formas, y el fallback se interpone por una página que estaba a punto de ser reemplazada. Dentro de una página, aplica alcance apretado.

Cuando un chunk falla al cargar

Planifica para el chunk que nunca llega. Una descarga fallida rechaza la promesa del loader, y Suspense cubre solo el estado pendiente: React relanza el rechazo durante el renderizado, y un error de renderizado que nada atrapa desmonta el árbol completo.

Empareja el límite con un error boundary, un componente que atrapa un error de renderizado debajo de él y muestra UI fallback en su lugar. La versión incorporada de React es lo único en este manual que aún tiene que ser un componente de clase, ya que no hay equivalente de hook, por eso la mayoría de proyectos instalan react-error-boundary y envuelven las rutas con su ErrorBoundary en cambio.

La causa usual es un deploy obsoleto, donde una pestaña abierta solicita un chunk con hash que la nueva compilación ya no sirve, y lo que el usuario necesita allí es una recarga de página en lugar de un reintento.

Observa también los waterfalls: una ruta lazy que comienza a traer datos solo después de que su chunk llega serializa dos viajes redondos. Las APIs de datos de React Router, donde un loader de ruta se ejecuta antes del renderizado, o precarga al intentar (comenzar el import() al pasar el cursor o enfocar un enlace, antes de que el renderizado lo necesite) colapsan eso. Llamar al mismo import('./ProductsList') temprano es seguro; el registro de módulos lo desduplica, y el renderizado lazy posterior encuentra el chunk ya en vuelo o hecho.

JunoDescarga código solo cuando lo necesitas Normalmente el navegador tiene que descargar todo el JavaScript de tu app antes de mostrar algo, y una feature grande puede mantener todo lo demás rehén. La división de código te permite decir "carga esta parte más tarde, solo si el usuario lo pide."

Envuelves un import dinámico en lazy, pones el componente dentro de Suspense con un fallback como un mensaje de carga, y React muestra el fallback mientras el código descarga. El resto de tu app aparece de inmediato.

JunoDescarga código solo cuando lo necesitasconst Page = lazy(() => import('./Page')) te da un componente cuyo código vive en su propio chunk, descargado en el primer renderizado; el archivo debe exportar por defecto el componente, y un límite Suspense con un fallback cubre la descarga.

Haz de las rutas tus puntos de división por defecto: cada página un componente lazy, un límite alrededor de la tabla de rutas. Salta dividir componentes minúsculos y UI arriba del fold, ya que cada chunk cuesta una petición.

JunoDescarga código solo cuando lo necesitas El bundler corta un chunk en cada sitio de llamada import() en tiempo de compilación; lazy lanza la promesa del loader en el primer renderizado, el límite Suspense más cercano muestra su fallback, y el módulo resuelto se cachea así que renderizados posteriores son síncronos.

Aplica alcance a los límites para controlar cuánta UI una suspensión en blanco, y precarga al intentar o deja que el router traiga los datos de una ruta antes del renderizado para evitar waterfalls chunk-luego-datos. Las llamadas import() duplicadas se desduplican, así que calentar un chunk temprano es gratis.

Siguiente: Autenticación, donde sesiones, tokens y ejecución del lado del servidor se unen.