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

Rotas aninhadas e layouts

Roteamento cobriu a versão simples da história: uma Route por página, cada uma com seu próprio path e element. Aplicações reais rapidamente crescem além disso, porque as URLs se aninham. Um caminho como /host/income tem duas partes, e frequentemente a página no caminho mais profundo mantém a maior parte da página no caminho mais raso: a mesma navbar, as mesmas abas de seção, com novo conteúdo substituído abaixo. Rotas aninhadas são como React Router expressa isso. Você aninha elementos Route dentro um do outro, e o roteador renderiza toda a cadeia correspondente de uma vez, com parents envolvendo children.

Então "rotas aninhadas" significa duas coisas relacionadas. Os segmentos de URL se aninham, e a interface se aninha com eles: partes da página persistem enquanto você navega mais profundamente, e apenas a parte interna muda. A segunda é o verdadeiro ganho.

Aninhando rotas e renderizando children com Outlet

Route funciona como um elemento self-closing, e também funciona com children. Seus children devem ser outros elementos Route. Quando uma rota child corresponde, React Router renderiza o elemento do parent primeiro, e o parent decide onde o elemento do child aparece renderizando um Outlet.

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

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Home />} />
          <Route path="about" element={<About />} />
          <Route path="vans" element={<Vans />} />
        </Route>
      </Routes>
    </BrowserRouter>
  )
}
jsx
import { Outlet } from 'react-router-dom'
import Header from './Header'

export default function Layout() {
  return (
    <>
      <Header />
      <Outlet />
    </>
  )
}

Outlet abre um buraco no elemento do parent onde o child correspondente é renderizado. Ele faz o mesmo papel que a prop children faz em composição: o parent renderiza a estrutura compartilhada e marca o local onde a parte variável vai. A diferença é que React Router preenche o buraco, escolhendo qual rota child corresponde à URL atual. Sem um Outlet, o elemento do parent toma conta da página e o child correspondente é renderizado em lugar nenhum, que é a primeira coisa a verificar quando uma página aninhada fica em branco.

A rota externa acima é uma rota de layout: tem um element e nenhum path. Uma rota sem caminho corresponde a toda URL que seus children correspondem, então Layout envolve toda página, que é exatamente onde o chrome do site inteiro como header e footer pertence. Um header renderizado ao lado de Routes faz o mesmo trabalho enquanto o aplicativo tem exatamente um layout; rotas de layout são o que deixa uma seção como /host trocar o chrome diferente sem tocar nas páginas abaixo. A opção a evitar é repetir o header dentro de cada componente de página.

Layouts com um caminho

Uma rota de layout também pode carregar um path, que limita a interface compartilhada a uma seção do aplicativo. O projeto VanLife do curso adiciona uma seção /host com sua própria nav secundária, então as páginas do host recebem um segundo layout aninhado dentro do primeiro:

jsx
<Route element={<Layout />}>
  <Route path="/" element={<Home />} />
  <Route path="about" element={<About />} />
  <Route path="host" element={<HostLayout />}>
    <Route path="income" element={<Income />} />
    <Route path="reviews" element={<Reviews />} />
  </Route>
</Route>

HostLayout renderiza a nav do host mais um Outlet, então em /host/income o roteador renderiza Layout, depois HostLayout em seu outlet, depois Income no outlet do host layout. Três rotas correspondem de uma vez, e cada parent emoldura a próxima. Layouts se empilham tão profundamente quanto o design exigir.

Rotas index

Há uma lacuna nessa configuração: HostLayout renderiza em /host, mas seu outlet não tem nada para mostrar lá, porque cada child adiciona um segmento ao caminho. A solução é uma rota index: substitua path pela prop index, e aquele child renderiza no outlet do parent no seu próprio caminho.

jsx
<Route index element={<Dashboard />} />

Agora /host mostra a nav do host com o dashboard dentro dela, e /host/income troca o dashboard pela página de income. Pense em index como "o child padrão": o que pertence ao outlet antes de qualquer segmento mais profundo ser adicionado. Funciona no nível superior também, onde <Route index element={<Home />} /> é a página inicial dentro do layout raiz, a mesma rota que o exemplo de abertura escreveu como path="/".

Note os caminhos dos children acima: income, sem barra inicial. Um caminho que começa com / é absoluto, medido a partir da raiz do site. Um caminho sem ele é relativo à sua rota parent, então o income aninhado significa /host/income. Isso mantém árvores de rotas profundas mantíveis: renomeie host para admin em um lugar e todo caminho descendente segue.

Links recebem o mesmo tratamento. Um Link renderizado dentro de um elemento roteado resolve caminhos relativos contra a rota que o renderizou, então dentro de HostLayout a nav pode droppar o prefixo /host inteiramente:

jsx
import { Link, Outlet } from 'react-router-dom'

export default function HostLayout() {
  return (
    <>
      <nav>
        <Link to=".">Dashboard</Link>
        <Link to="income">Income</Link>
        <Link to="reviews">Reviews</Link>
      </nav>
      <Outlet />
    </>
  )
}

Dois valores especiais pegam emprestado seu significado de caminhos de sistema de arquivos. to="." linka para a rota atual, que é como o link Dashboard targeta /host sem hardcodá-lo. to=".." sobe para cima, útil para links "voltar para a lista"; ele vem com uma sutileza, já que sobe uma rota em vez de um segmento de URL, o que começa a importar uma vez que uma única rota possua vários segmentos, e Route params and location o coloca em prática em um botão de voltar real.

NavLink e ancestor matching

Troque aqueles elementos Link por NavLink para estilizar a página atual, como o curso faz para a nav do host, e ancestor matching entra em jogo: o roteador conta todo ancestor da URL atual como correspondente, então o link Dashboard to="." relata como ativo em /host/income e /host/reviews também. Adicione end àquele link e ele permanece sem estilo em qualquer lugar abaixo de /host.

Passando dados para baixo com useOutletContext

Um layout frequentemente possui dados que seus children precisam. HostVanDetail no curso busca uma van e a mantém em state, depois renderiza uma nav de abas e um Outlet onde a página de info, pricing ou photos aparece. Props não podem alcançar através de um Outlet, já que o layout nunca renderiza aqueles componentes diretamente. A resposta do React Router é a prop context em Outlet, lida do outro lado com useOutletContext:

jsx
<Outlet context={{ currentVan }} />
jsx
import { useOutletContext } from 'react-router-dom'

export default function HostVanInfo() {
  const { currentVan } = useOutletContext()

  return <h4>Name: {currentVan.name}</h4>
}

Qualquer valor que você passe para context é o que useOutletContext retorna em qualquer child que preencha atualmente o outlet. Passar um objeto e fazer destructuring no lado receptor é a forma comum, e deixa espaço para adicionar mais valores depois. Se Context é familiar, isso se parecerá familiar também: o outlet atua como um provider limitado a exatamente um buraco na página.

Aninhar ou não aninhar

Aninhamento se justifica quando rotas compartilham interface. Esse é o teste. A URL sozinha pode tentá-lo a aninhar: /vans e /vans/:id compartilham um segmento, então por que não compartilhar uma rota? Mas em VanLife aquelas duas páginas não compartilham nada visível além do chrome do site inteiro, e aninhá-las força uma forma desajeitada: uma rota parent com um path e nenhum element, que volta para renderizar um Outlet vazio, mais um child index para renderizar a lista. Essa é mais estrutura para zero benefício.

Quando há interface compartilhada para manter na tela, aninhe e dê ao parent um elemento de layout com um Outlet. Quando a única motivação é evitar um segmento de caminho repetido, duas rotas flat são a escolha mais simples e clara.

Version note

The examples here import from react-router-dom, which works in both v6 and v7; Routing covers what v7 changed about the packages. The nested-route concepts are identical in both versions.

Matching é uma operação em árvore. React Router classifica cada branch da árvore de rotas contra a URL e renderiza toda a branch vencedora, elemento mais externo primeiro, cada Outlet recebendo o próximo elemento para baixo. Uma rota de layout sem caminho não contribui para matching; ela apenas injeta um elemento na cadeia, é por isso que pode envolver tudo. Isso também explica o modo de falha onde um elemento parent sem um Outlet silenciosamente engole seus children: o match teve sucesso, a branch tem três rotas de comprimento, mas a cadeia de render não tem buraco para continuar.

A resolução relativa segue a árvore de rotas em vez da string de URL, e essa distinção morde com segmentos dinâmicos. De um componente renderizado por <Route path="vans/:id"> aninhado sob host, to=".." resolve para /host, pulando tanto :id quanto vans, porque .. sobe uma rota e aquela rota única possui ambos segmentos. Quando você quer comportamento de segmento de URL, opte por <Link to=".." relative="path">. Nenhum padrão está errado; linking relativo a rota sobrevive a renames de caminho, enquanto linking relativo a caminho combina com intuição de usuário para botões de voltar.

useOutletContext é literalmente context por baixo: Outlet envolve o child correspondente em um provider. Isso lhe dá comportamento de re-render de context, então um novo literal de objeto passado para a prop context significa que todo componente lendo useOutletContext renderiza novamente sempre que o layout faz, o que raramente é um problema em escala de página já que navegar troca a subtree de qualquer forma.

Escolha entre os três mecanismos de entrega por scope: props onde você renderiza o child você mesmo, outlet context para um layout alimentando qualquer página que preencha seu buraco, e full context quando componentes fora da subtree roteada também precisam do valor. Uma caução estrutural: outlet context acopla children ao shape do seu layout parent. Um componente que lê useOutletContext só funciona montado sob um layout que fornece aquele shape, então mantenha o valor de context pequeno e o trate como parte da API pública do layout.

JunoAninhe rotas para manter a interface compartilhada na página Quando páginas compartilham partes da tela, como uma navbar que deve permanecer fixa enquanto o conteúdo abaixo muda, você pode aninhar uma rota dentro de outra. A rota parent renderiza a parte compartilhada e coloca um Outlet onde a página child deve aparecer.

Uma rota com um element e nenhum path é um layout que envolve tudo, e uma rota index é o child padrão mostrado no endereço do parent.

Se duas páginas não compartilham nada na tela, não há necessidade de aninhá-las.

JunoAninhe rotas para manter a interface compartilhada na página Envolva rotas child em um parent Route, dê ao parent um elemento de layout com um Outlet, e marque o child padrão com index. Remova barras iniciais para que caminhos e links se resolvam relativos ao seu parent, o que torna renames mudanças de uma linha, e use to="." para linkar para a rota atual.

Quando um layout possui dados que seus outlet children precisam, passe com a prop context de Outlet e leia com useOutletContext.

Aninhe para interface compartilhada; pule quando a única vantagem é uma string de caminho mais curta.

JunoAninhe rotas para manter a interface compartilhada na página O roteador faz match de uma branch da árvore de rotas e a renderiza outermost-in, cada Outlet recebendo o próximo elemento, com layouts sem caminho injetando elementos sem afetar matching. Links relativos se resolvem contra a árvore de rotas, então .. sobe uma rota mesmo quando aquela rota possui múltiplos segmentos de URL; relative="path" opta por climbing de segmento.

Outlet context é um provider limitado, conveniente para dados de layout-para-página mas um ponto de acoplamento, então mantenha seu shape pequeno e estável.

Reserve aninhamento para interface compartilhada; um parent com um path e nenhum element é um sinal de que a versão flat estava bem.

Next up: Route params and location, where a single route serves many pages.