136 lines
3.9 KiB
Markdown
136 lines
3.9 KiB
Markdown
|
|
---
|
|||
|
|
tags: [frontend, react, react-router, routing, nested-routes]
|
|||
|
|
create time: 2026-04-25 15:30
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Outlet:嵌套路由的出口 / 占位符
|
|||
|
|
|
|||
|
|
## 概述
|
|||
|
|
|
|||
|
|
`Outlet` 是 React Router 中用于"嵌套路由占位"的组件,负责在父组件布局中预留一个位置,由子路由匹配到的组件来渲染。
|
|||
|
|
|
|||
|
|
## 正文
|
|||
|
|
|
|||
|
|
### 为什么要用 Outlet
|
|||
|
|
|
|||
|
|
假设有这样的路由结构:
|
|||
|
|
|
|||
|
|
- `/dashboard`
|
|||
|
|
- `/courses`
|
|||
|
|
- `/students`
|
|||
|
|
|
|||
|
|
它们都共享同一个外层布局——左侧导航栏、顶部标题栏、主内容区域。如果不使用 `Outlet`,每个页面组件都要重复写一遍布局结构。
|
|||
|
|
|
|||
|
|
有了 `Outlet`,可以把公共布局抽到一个**父路由组件**中,子页面内容自动渲染到 `Outlet` 位置,不需要重复代码。
|
|||
|
|
|
|||
|
|
> 💡 **想一想**:如果每个子页面都自己写侧边栏和顶栏,当侧边栏需要修改时,需要改几个文件?用了 `Outlet` 后又需要改几个?
|
|||
|
|
|
|||
|
|
### 路由结构对比
|
|||
|
|
|
|||
|
|
```mermaid
|
|||
|
|
graph TB
|
|||
|
|
subgraph "不使用 Outlet"
|
|||
|
|
A1[LoginPage] --> B1[浏览器]
|
|||
|
|
A2[DashboardPage] --> B1
|
|||
|
|
A3[CoursesPage] --> B1
|
|||
|
|
A4[StudentsPage] --> B1
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
subgraph "使用 Outlet"
|
|||
|
|
Layout[Layout 父组件<br/>header + aside + Outlet] --> B2[浏览器]
|
|||
|
|
subgraph "Layout 内部"
|
|||
|
|
H[<header>]
|
|||
|
|
S[<aside>]
|
|||
|
|
O[<Outlet />]
|
|||
|
|
H --> O
|
|||
|
|
S --> O
|
|||
|
|
end
|
|||
|
|
O --> C1[DashboardPage]
|
|||
|
|
O --> C2[CoursesPage]
|
|||
|
|
O --> C3[StudentsPage]
|
|||
|
|
end
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
左边:每个页面独立渲染,布局代码重复。右边:父组件统一管理布局,子页面只关心自己的内容。
|
|||
|
|
|
|||
|
|
### 布局组件实现
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
import { Outlet } from 'react-router-dom'
|
|||
|
|
|
|||
|
|
function Layout() {
|
|||
|
|
return (
|
|||
|
|
<div className="app-layout">
|
|||
|
|
<header>顶部栏</header> {/* 固定不变 */}
|
|||
|
|
<aside>侧边栏</aside> {/* 固定不变 */}
|
|||
|
|
<main>
|
|||
|
|
<Outlet /> {/* 子路由内容渲染到这里 */}
|
|||
|
|
</main>
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**解读**:`Layout` 组件中,`<header>` 和 `<aside>` 是固定结构,始终显示;`<Outlet />` 是一个"占位符",React Router 会自动把当前路径匹配到的子组件渲染到它的位置。
|
|||
|
|
|
|||
|
|
### 路由声明方式
|
|||
|
|
|
|||
|
|
**JSX 声明式写法**:
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
<Route element={<Layout />}> {/* 父路由:负责布局 */}
|
|||
|
|
<Route path="/dashboard" element={<DashboardPage />} />
|
|||
|
|
<Route path="/courses" element={<CoursesPage />} />
|
|||
|
|
<Route path="/students" element={<StudentsPage />} />
|
|||
|
|
</Route>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**配置文件写法**:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const router = createBrowserRouter([
|
|||
|
|
{
|
|||
|
|
path: '/',
|
|||
|
|
element: <Layout />, {/* 父路由组件 */}
|
|||
|
|
children: [ {/* 子路由列表 */}
|
|||
|
|
{ path: 'dashboard', element: <DashboardPage /> },
|
|||
|
|
{ path: 'courses', element: <CoursesPage /> },
|
|||
|
|
{ path: 'students', element: <StudentsPage /> },
|
|||
|
|
],
|
|||
|
|
},
|
|||
|
|
])
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
两种方式等价,选择哪种取决于项目规模和个人偏好。
|
|||
|
|
|
|||
|
|
### Routes vs Outlet
|
|||
|
|
|
|||
|
|
| 特性 | `<Routes>` | `<Outlet />` |
|
|||
|
|
|------|-----------|-------------|
|
|||
|
|
| 职责 | 匹配路径,决定显示哪个组件 | 在父组件中预留子组件渲染位置 |
|
|||
|
|
| 在哪用 | 任意需要路由判断的地方 | 只在父路由组件内部 |
|
|||
|
|
| 类比 | 调度中心 | 出口 / 窗口 |
|
|||
|
|
|
|||
|
|
```tsx
|
|||
|
|
// Routes:路径 → 组件 的映射表
|
|||
|
|
<Routes>
|
|||
|
|
<Route path="/courses" element={<CoursesPage />} />
|
|||
|
|
</Routes>
|
|||
|
|
|
|||
|
|
// Outlet:父组件留给子组件的"位置"
|
|||
|
|
function Layout() {
|
|||
|
|
return (
|
|||
|
|
<div>
|
|||
|
|
<Header />
|
|||
|
|
<Outlet /> {/* 子组件会出现在这里 */}
|
|||
|
|
</div>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
> 💡 **想一想**:如果父路由组件里没有放 `<Outlet />`,子路由对应的页面还能显示出来吗?(不能,子组件找不到挂载点,不会被渲染到页面上。)
|
|||
|
|
|
|||
|
|
## 关联笔记
|
|||
|
|
|
|||
|
|
- [[路由原理]] - BrowserRouter、Routes、Route 等核心路由组件
|