Suspense
Suspense
es un componente de React que muestra una interfaz previa o fallback hasta que sus hijos hayan terminado de cargar.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
Uso
Visualización de una interfaz previa mientras algo se está cargando
Puedes envolver cualquier parte de la aplicación con un componente Suspense. Si los datos o el código en su hijo aún no se ha cargado, React pasará a renderizar la prop fallback
en su lugar. Por ejemplo:
<>
<Post />
<Suspense fallback={<LoadingSpinner />}>
<Comments />
</Suspense>
</>
Supongamos que Comments
tarda más en cargarse que Post
. Sin una barrera Suspense, React no podría mostrar ninguno de los dos componentes hasta que ambos se hubieran cargado: Post
estaría bloqueado por Comments
.
Debido a la barrera Suspense, Post
no necesita esperar a Comments
. React renderiza LoadingSpinner
en su lugar. Una vez que Comments
termina de cargar, React reemplaza LoadingSpinner
con Comments
.
Suspense nunca mostrará “agujeros” involuntarios en tu contenido. Por ejemplo, si PhotoAlbums
se ha cargado pero Notes
no, con la estructura de abajo, seguirá mostrando un LoadingSpinner
en lugar de todo el Grid
:
<>
<ProfileHeader />
<Suspense fallback={<LoadingSpinner />}>
<Grid>
<PhotoAlbums />
<Notes />
</Grid>
</Suspense>
</>
Para revelar el contenido anidado a medida que se carga, es necesario añadir más barreras Suspense.
Revelar el contenido anidado mientras se carga
Cuando un componente se suspende, solo se activa el fallback de la barrera Suspense padre más cercana. Esto significa que puedes anidar varias barreras Suspense para crear una secuencia de carga. El fallback de cada barrera Suspense se rellenará a medida que el siguiente nivel de contenido esté disponible.
Para ilustrarlo, considera el siguiente ejemplo:
<Suspense fallback={<BigSpinner />}>
<MainContent>
<Post />
<Suspense fallback={<CommentsGlimmer />}>
<Comments />
</Suspense>
</MainContent>
</Suspense>
La secuencia será:
- Si
Post
aún no se ha cargado,BigSpinner
se muestra en lugar de toda el área de contenido principal. - Una vez que
Post
termina de cargar,BigSpinner
es reemplazado por el contenido principal. - Si aún no se ha cargado
Comments
, se muestraCommentsGlimmer
en su lugar. - Finalmente, una vez que
Comments
termina de cargarse, reemplaza aCommentsGlimmer
.
Componentes de carga diferida con Suspense
La API lazy
está fuertemente vinculada a Suspense. Cuando se renderiza un componente importado con lazy
, se suspenderá si no se ha cargado todavía. Esto te permite mostrar un indicador de carga mientras el código de el componente se está cargando.
import { lazy, Suspense, useState } from 'react';
const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));
function MarkdownEditor() {
const [showPreview, setShowPreview] = useState(false);
// ...
return (
<>
...
{showPreview && (
<Suspense fallback={<Loading />}>
<h2>Preview</h2>
<MarkdownPreview />
</Suspense>
)}
</>
);
}
En este ejemplo, el código de MarkdownPreview
no se cargará hasta que intentes renderizarlo. Si MarkdownPreview
no se ha cargado aún, se mostrará Loading
en su lugar. Prueba a marcar la casilla de verificación:
import { useState, Suspense, lazy } from 'react'; import Loading from './Loading.js'; const MarkdownPreview = lazy(() => delayForDemo(import('./MarkdownPreview.js'))); export default function MarkdownEditor() { const [showPreview, setShowPreview] = useState(false); const [markdown, setMarkdown] = useState('Hello, **world**!'); return ( <> <textarea value={markdown} onChange={e => setMarkdown(e.target.value)} /> <label> <input type="checkbox" checked={showPreview} onChange={e => setShowPreview(e.target.checked)} /> Show preview </label> <hr /> {showPreview && ( <Suspense fallback={<Loading />}> <h2>Preview</h2> <MarkdownPreview markdown={markdown} /> </Suspense> )} </> ); } // Add a fixed delay so you can see the loading state function delayForDemo(promise) { return new Promise(resolve => { setTimeout(resolve, 2000); }).then(() => promise); }
Esta demo se carga con un retraso artificial. La próxima vez que desmarques y marques la casilla de verificación, Preview
se almacenará en la caché, por lo que no se mostrará el estado de carga. Para volver a ver el estado de carga, haz clic en “Reiniciar” en el sandbox.
Referencia
Suspense
Props
children
: La interfaz que realmente se pretende renderizar. Sichildren
se suspende mientras se renderiza, la barrera Suspense pasará a renderizarfallback
.fallback
: Una interfaz alternativa a renderizar en lugar de la interfaz real si esta no ha terminado de cargar. Se acepta cualquier nodo React válido, aunque en la práctica, un fallback es una vista ligera de relleno, como un spinner de carga o un esqueleto. Suspense cambiará automáticamente afallback
cuandochildren
se suspenda, y volverá achildren
cuando los datos estén listos. Sifallback
se suspende mientras se renderiza, activará la barrera de Suspense padre más cercana.
Advertencias
- React no preserva ningún estado para los renderizados que se suspendieron antes de que pudieran montarse por primera vez. Cuando el componente se haya cargado, React volverá a intentar renderizar el árbol suspendido desde cero.
- Si la suspensión estaba mostrando contenido para el árbol, pero luego se volvió a suspender, el
fallback
se mostrará de nuevo a menos que la actualización que lo causó fuese causada porstartTransition
ouseDeferredValue
. - Si React necesita ocultar el contenido ya visible porque se suspendió de nuevo, limpiará los Efectos de layout en el árbol de contenido. Cuando el contenido esté listo para mostrarse de nuevo, React disparará los Efectos de layout de nuevo. Esto le permite asegurarse de que los Efectos que miden el diseño del DOM no intentan hacerlo mientras el contenido está oculto.
- React incluye optimizaciones internas como Renderizado en el servidor con Streaming e Hidratación selectiva que se integran con Suspense. Puedes leer una visión general de la arquitectura y ver esta charla técnica para conocer más.
Solución de problemas
¿Cómo puedo evitar que la interfaz de usuario sea sustituida por un fallback durante una actualización?
Reemplazar la interfaz de usuario visible por una de reserva crea una experiencia de usuario discordante. Esto puede ocurrir cuando una actualización hace que un componente se suspenda, y la barrera Suspense más cercana ya está mostrando contenido al usuario.
Para evitar que esto ocurra, marca la actualización como no urgente utilizando startTransition
. Durante una transición, React esperará hasta que se hayan cargado suficientes datos para evitar que aparezca un fallback no deseado:
function handleNextPageClick() {
// If this update suspends, don't hide the already displayed content
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
Esto evitará ocultar el contenido existente. Sin embargo, cualquier barrera Suspense
recién renderizada seguirá mostrando inmediatamente los fallbacks para evitar el bloqueo de la UI y dejar que el usuario vea el contenido a medida que esté disponible.
React sólo evitará los “fallbacks” no deseados durante las actualizaciones no urgentes. No retrasará un renderizado si es el resultado de una actualización urgente. Debes indicarlo con una API como startTransition
o useDeferredValue
.
Si tu router está integrado con Suspense, debería envolver sus actualizaciones en startTransition
automáticamente.