<Suspense>
<Suspense>
permite que você exiba um fallback até que seus filhos tenham terminado de carregar.
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
- Referência
- Uso
- Exibindo um fallback enquanto o conteúdo está carregando
- Revelando conteúdo junto de uma só vez
- Exibindo conteúdo obsoleto enquanto o conteúdo novo está carregando
- Impedindo que conteúdo já revelado seja ocultado
- Indicando que uma Transição está acontecendo
- Resetando fronteiras de Suspense na navegação
- Fornecendo um fallback para erros de servidor e conteúdo somente do cliente
- Solução de Problemas
Referência
<Suspense>
Props
children
: A interface real que você pretende renderizar. Sechildren
suspender durante a renderização, a fronteira do Suspense mudará para renderizarfallback
.fallback
: Uma interface alternativa para renderizar no lugar da interface real, se esta não tiver terminado de carregar. Qualquer nó React válido é aceito, embora na prática, um fallback seja uma visualização placeholder leve, como um spinner de carregamento ou esqueleto. O Suspense mudará automaticamente parafallback
quandochildren
suspender, e voltará parachildren
quando os dados estiverem prontos. Sefallback
suspender durante a renderização, ele ativará a fronteira de Suspense pai mais próxima.
Ressalvas
- O React não preserva nenhum estado para renders que foram suspensas antes de poderem montar pela primeira vez. Quando o componente for carregado, o React tentará renderizar a árvore suspensa do zero.
- Se o Suspense estiver exibindo conteúdo para a árvore, mas depois suspender novamente, o
fallback
será exibido novamente, a menos que a atualização que o causou tenha sido causada porstartTransition
ouuseDeferredValue
. - Se o React precisar ocultar o conteúdo já visível porque ele suspendeu novamente, ele limpará Effects de layout na árvore de conteúdo. Quando o conteúdo estiver pronto para ser exibido novamente, o React acionará os Effects de layout novamente. Isso garante que os Effects que medem o layout do DOM não tentem fazer isso enquanto o conteúdo estiver oculto.
- O React inclui otimizações internas como Streaming Server Rendering e Selective Hydration que estão integradas com o Suspense. Leia uma visão geral arquitetônica e assista a uma palestra técnica para saber mais.
Uso
Exibindo um fallback enquanto o conteúdo está carregando
Você pode envolver qualquer parte de sua aplicação com uma fronteira de Suspense:
<Suspense fallback={<Loading />}>
<Albums />
</Suspense>
O React exibirá seu fallback de carregamento até que todo o código e dados necessários por os filhos tenham sido carregados.
No exemplo abaixo, o componente Albums
suspende enquanto busca a lista de álbuns. Até que esteja pronto para renderizar, o React muda a fronteira de Suspense mais próxima acima para mostrar o fallback—seu componente Loading
. Então, quando os dados carregam, o React oculta o fallback Loading
e renderiza o componente Albums
com dados.
import { Suspense } from 'react'; import Albums from './Albums.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Albums artistId={artist.id} /> </Suspense> </> ); } function Loading() { return <h2>🌀 Carregando...</h2>; }
Revelando conteúdo junto de uma só vez
Por padrão, toda a árvore dentro do Suspense é tratada como uma única unidade. Por exemplo, mesmo que apenas um desses componentes suspender enquanto espera por alguns dados, todos eles juntos serão substituídos pelo indicador de carregamento:
<Suspense fallback={<Loading />}>
<Biography />
<Panel>
<Albums />
</Panel>
</Suspense>
Então, depois que todos estiverem prontos para serem exibidos, aparecerão juntos de uma só vez.
No exemplo abaixo, tanto Biography
quanto Albums
buscam alguns dados. No entanto, como estão agrupados sob uma única fronteira de Suspense, esses componentes sempre “aparecem” juntos ao mesmo tempo.
import { Suspense } from 'react'; import Albums from './Albums.js'; import Biography from './Biography.js'; import Panel from './Panel.js'; export default function ArtistPage({ artist }) { return ( <> <h1>{artist.name}</h1> <Suspense fallback={<Loading />}> <Biography artistId={artist.id} /> <Panel> <Albums artistId={artist.id} /> </Panel> </Suspense> </> ); } function Loading() { return <h2>🌀 Carregando...</h2>; }
As fronteiras de Suspense permitem que você coordene quais partes da sua interface devem sempre “aparecer” juntas ao mesmo tempo, e quais partes devem revelar progressivamente mais conteúdo em uma sequência de estados de carregamento. Você pode adicionar, mover ou excluir fronteiras de Suspense em qualquer lugar da árvore sem afetar o comportamento do resto do seu aplicativo.
Não coloque uma fronteira de Suspense ao redor de cada componente. As fronteiras de Suspense não devem ser mais granulares do que a sequência de carregamento que você deseja que o usuário experimente. Se você trabalhar com um designer, pergunte a eles onde os estados de carregamento devem ser colocados—é provável que eles já os incluíram em suas wireframes de design.
Exibindo conteúdo obsoleto enquanto o conteúdo novo está carregando
Neste exemplo, o componente SearchResults
suspende enquanto busca os resultados da pesquisa. Digite "a"
, espere pelos resultados e depois edite para "ab"
. Os resultados para "a"
serão substituídos pelo fallback de carregamento.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Pesquisar álbuns: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Carregando...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
Um padrão alternativo comum de interface é adiar a atualização da lista e manter exibindo os resultados anteriores até que os novos resultados estejam prontos. O Hook useDeferredValue
permite que você passe uma versão adiada da consulta:
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Pesquisar álbuns:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Carregando...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
A query
será atualizada imediatamente, então a entrada exibirá o novo valor. No entanto, a deferredQuery
manterá seu valor anterior até que os dados sejam carregados, então SearchResults
mostrará os resultados obsoletos por um tempo.
Para deixar mais óbvio para o usuário, você pode adicionar uma indicação visual quando a lista de resultados obsoleta é exibida:
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1
}}>
<SearchResults query={deferredQuery} />
</div>
Digite "a"
no exemplo abaixo, espere pelos resultados carregarem e, em seguida, edite a entrada para "ab"
. Note como, em vez do fallback do Suspense, agora você vê a lista de resultados obsoletos esmaecida até que os novos resultados tenham carregado:
import { Suspense, useState, useDeferredValue } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <> <label> Pesquisar álbuns: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Carregando...</h2>}> <div style={{ opacity: isStale ? 0.5 : 1 }}> <SearchResults query={deferredQuery} /> </div> </Suspense> </> ); }
Impedindo que conteúdo já revelado seja ocultado
Quando um componente suspende, a fronteira de Suspense pai mais próxima muda para mostrar o fallback. Isso pode levar a uma experiência do usuário abrupta se já estava exibindo algum conteúdo. Tente pressionar este botão:
import { Suspense, useState } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); function navigate(url) { setPage(url); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Carregando...</h2>; }
Quando você pressionou o botão, o componente Router
renderizou ArtistPage
em vez de IndexPage
. Um componente dentro de ArtistPage
suspendeu, então a fronteira de Suspense mais próxima começou a mostrar o fallback. A fronteira mais próxima de Suspense estava perto da raiz, então todo o layout do site foi substituído por BigSpinner
.
Para impedir isso, você pode marcar a atualização de navegação como uma Transição com startTransition
:
function Router() {
const [page, setPage] = useState('/');
function navigate(url) {
startTransition(() => {
setPage(url);
});
}
// ...
Isso diz ao React que a transição do estado não é urgente, e é melhor continuar exibindo a página anterior em vez de ocultar qualquer conteúdo já revelado. Agora, clicar no botão “aguarda” o carregamento da Biography
:
import { Suspense, startTransition, useState } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Carregando...</h2>; }
A Transição não espera que todo o conteúdo carregue. Ela espera o tempo suficiente para evitar ocultar conteúdo já revelado. Por exemplo, o layout do site já estava revelado, portanto seria ruim ocultá-lo atrás de um spinner de carregamento. No entanto, a fronteira de Suspense aninhada ao redor de Albums
é nova, então a Transição não espera por ela.
Indicando que uma Transição está acontecendo
No exemplo acima, uma vez que você clica no botão, não há indicação visual de que uma navegação está em andamento. Para adicionar um indicador, você pode substituir startTransition
por useTransition
, que fornece um valor booleano isPending
. No exemplo abaixo, ele é usado para mudar o estilo do cabeçalho do site enquanto uma Transição está acontecendo:
import { Suspense, useState, useTransition } from 'react'; import IndexPage from './IndexPage.js'; import ArtistPage from './ArtistPage.js'; import Layout from './Layout.js'; export default function App() { return ( <Suspense fallback={<BigSpinner />}> <Router /> </Suspense> ); } function Router() { const [page, setPage] = useState('/'); const [isPending, startTransition] = useTransition(); function navigate(url) { startTransition(() => { setPage(url); }); } let content; if (page === '/') { content = ( <IndexPage navigate={navigate} /> ); } else if (page === '/the-beatles') { content = ( <ArtistPage artist={{ id: 'the-beatles', name: 'The Beatles', }} /> ); } return ( <Layout isPending={isPending}> {content} </Layout> ); } function BigSpinner() { return <h2>🌀 Carregando...</h2>; }
Uma Transição não espera que todos os conteúdos carreguem. Ela apenas espera o tempo suficiente para evitar ocultar conteúdo já revelado. Por exemplo, o layout do site já estava visível, portanto seria ruim ocultá-lo atrás de um carregando spinner. No entanto, a fronteira de Suspense aninhada em Albums
é nova, então a Transição não espera por ela.
Resetando fronteiras de Suspense na navegação
Durante uma Transição, o React evitará ocultar conteúdo já revelado. No entanto, se você navegar para uma rota com parâmetros diferentes, pode querer informar ao React que é conteúdo diferente. Você pode expressar isso com uma key
:
<ProfilePage key={queryParams.id} />
Imagine que você está navegando dentro de uma página de perfil de usuário, e algo suspende. Se essa atualização estiver envolta em uma Transição, não acionará o fallback para conteúdo já visível. Esse é o comportamento esperado.
No entanto, agora imagine que você está navegando entre dois perfis de usuário diferentes. Nesse caso, faz sentido mostrar o fallback. Por exemplo, a linha do tempo de um usuário é conteúdo diferente da linha do tempo de outro usuário. Ao especificar uma key
, você garante que o React trate perfis de usuários diferentes como componentes diferentes e redefina as fronteiras do Suspense durante a navegação. Roteadores integrados com Suspense devem fazer isso automaticamente.
Fornecendo um fallback para erros de servidor e conteúdo somente do cliente
Se você usar uma das APIs de renderização de servidor com streaming (ou um framework que dependa delas), o React também usará suas fronteiras <Suspense>
para lidar com erros no servidor. Se um componente lançar um erro no servidor, o React não abortará a renderização do servidor. Em vez disso, ele encontrará o componente <Suspense>
mais próximo acima e incluirá seu fallback (como um spinner) no HTML gerado do servidor. O usuário verá um spinner no início.
No cliente, o React tentará renderizar o mesmo componente novamente. Se ocorrer um erro no cliente também, o React lançará o erro e exibirá o error boundary mais próximo. No entanto, se não houver erro no cliente, o React não exibirá o erro ao usuário, uma vez que o conteúdo foi eventualmente exibido com sucesso.
Você pode usar isso para optar por alguns componentes não renderizáveis no servidor. Para fazer isso, lance um erro no ambiente do servidor e, em seguida, envolva-os em uma fronteira <Suspense>
para substituir seu HTML por fallbacks:
<Suspense fallback={<Loading />}>
<Chat />
</Suspense>
function Chat() {
if (typeof window === 'undefined') {
throw Error('O Chat deve ser renderizado apenas no cliente.');
}
// ...
}
O HTML do servidor incluirá o indicador de carregamento. Ele será substituído pelo componente Chat
no cliente.
Solução de Problemas
Como posso impedir que a interface seja substituída por um fallback durante uma atualização?
Substituir a interface visível por um fallback cria uma experiência do usuário abrupta. Isso pode acontecer quando uma atualização faz com que um componente suspenda, e a fronteira de Suspense mais próxima já está exibindo conteúdo para o usuário.
Para evitar que isso aconteça, marque a atualização como não urgente usando startTransition
. Durante uma Transição, o React aguardará até que dados suficientes tenham sido carregados para evitar que um fallback indesejado apareça:
function handleNextPageClick() {
// Se esta atualização suspender, não oculte o conteúdo já exibido
startTransition(() => {
setCurrentPage(currentPage + 1);
});
}
Isso evitará ocultar conteúdo existente. No entanto, qualquer nova fronteira de Suspense renderizada será exibida imediatamente com fallbacks para evitar bloquear a interface e permitir que o usuário veja o conteúdo à medida que ele se torne disponível.
O React só impedirá fallbacks indesejados durante atualizações não urgentes. Ele não atrasará uma renderização se for resultado de uma atualização urgente. Você deve optar por uma API como startTransition
ou useDeferredValue
.
Se o seu roteador estiver integrado com o Suspense, ele deve envolver suas atualizações em startTransition
automaticamente.