布局和模板
特殊文件 layout.js 和 template.js 允许你创建在多个 路由 之间共享的 UI。本页将指导你如何以及何时使用这些特殊文件。
布局
布局是在多个路由之间共享的 UI。在导航时,布局会保持状态,保持交互性,并且不会重新渲染。布局也可以 嵌套。
你可以通过从 layout.js
文件中默认导出一个 React 组件来定义布局。该组件应接受一个 children
属性,在渲染过程中,这个属性将被填充为子布局(如果存在)或页面。
例如,该布局将与 /dashboard
和 /dashboard/settings
页面共享:
根布局(必需)
根布局定义在 app
目录的顶层,适用于所有路由。这个布局是必需的,必须包含 html
和 body
标签,允许你修改从服务器返回的初始 HTML。
嵌套布局
默认情况下,文件夹层次结构中的布局是嵌套的,这意味着它们通过 children
属性包裹子布局。你可以通过在特定路由段(文件夹)内添加 layout.js
来嵌套布局。
例如,要为 /dashboard
路由创建一个布局,在 dashboard
文件夹中添加一个新的 layout.js
文件:
如果你要组合上面的两个布局,根布局(app/layout.js
)将包裹仪表板布局(app/dashboard/layout.js
),后者将包裹 app/dashboard/*
内的路由段。
这两个布局将如下嵌套:
值得注意的是:
- 布局可以使用
.js
、.jsx
或.tsx
文件扩展名。- 只有根布局可以包含
<html>
和<body>
标签。- 当在同一文件夹中定义了
layout.js
和page.js
文件时,布局将包裹该页面。- 布局默认是 服务器组件,但可以设置为 客户端组件。
- 布局可以获取数据。查看 数据获取 部分了解更多信息。
- 在父布局和其子组件之间传递数据是不可能的。但是,你可以在一个路由中多次获取相同的数据,React 将 自动删除重复请求,而不会影响性能。
- 布局无法访问
pathname
( 了解更多 )。但是,导入的客户端组件可以使用usePathname
hook 访问 pathname。- 布局无法访问其下方的路由段。要访问所有路由段,你可以在客户端组件中使用
useSelectedLayoutSegment
或useSelectedLayoutSegments
。- 你可以使用 路由组 来选择特定路由段加入或退出共享布局。
- 你可以使用 路由组 来创建多个根布局。查看 这里的示例。
- 从
pages
目录迁移: 根布局替代了_app.js
和_document.js
文件。 查看迁移指南。
模板
模板与布局类似,都是包裹子布局或页面。但与在路由间保持状态的布局不同,模板在导航时会为其每个子组件创建一个新实例。这意味着当用户在共享模板的路由之间导航时,会挂载子组件的新实例,重新创建 DOM 元素,客户端组件中的状态不会保留,并且会重新同步 effects。
在某些情况下,你可能需要这些特定的行为,此时模板会是比布局更合适的选择。例如:
- 在导航时重新同步
useEffect
。 - 在导航时重置子客户端组件的状态。
可以通过从 template.js
文件中导出一个默认的 React 组件来定义模板。该组件应接受一个 children
属性。
在嵌套方面,template.js
在布局和其子组件之间渲染。以下是一个简化的输出:
示例
元数据
你可以使用 元数据 API 修改 <head>
HTML 元素,如 title
和 meta
。
元数据可以通过在 layout.js
或 page.js
文件中导出 metadata
对象 或 generateMetadata
函数 来定义。
值得注意的是:你不应在根布局中手动添加
<head>
标签,如<title>
和<meta>
。相反,应该使用 元数据 API,它可以自动处理高级需求,如流式传输和去重<head>
元素。
在 API 参考 中了解更多可用的元数据选项。
激活的导航链接
你可以使用 usePathname() hook 来确定导航链接是否处于激活状态。
由于 usePathname()
是一个客户端 hook,你需要将导航链接提取到一个客户端组件中,该组件可以导入到你的布局或模板中: