NextJS中文文档 - Layouts And Templates
特殊文件 layout.js 和 template.js 允许你创建在路由之间共享的 UI。本页将指导你如何以及何时使用这些特殊文件。
布局
布局是在多个路由之间共享的 UI。在导航过程中,布局会保持状态,保持交互性,不会重新渲染。布局也可以嵌套。
你可以通过从 layout.js
文件中默认导出一个 React 组件来定义布局。该组件应接受一个 children
属性,在渲染过程中,该属性将填充子布局(如果存在)或页面。
例如,布局将与 /dashboard
和 /dashboard/settings
页面共享:
tsx
export default function DashboardLayout({
children, // 将是页面或嵌套布局
}: {
children: React.ReactNode
}) {
return (
<section>
{/* 在此处包含共享 UI,例如标题或侧边栏 */}
<nav></nav>
{children}
</section>
)
}
jsx
export default function DashboardLayout({
children, // 将是页面或嵌套布局
}) {
return (
<section>
{/* 在此处包含共享 UI,例如标题或侧边栏 */}
<nav></nav>
{children}
</section>
)
}
根布局(必需)
根布局定义在 app
目录的顶层,适用于所有路由。这个布局是必需的,必须包含 html
和 body
标签,允许你修改从服务器返回的初始 HTML。
tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{/* 布局 UI */}
<main>{children}</main>
</body>
</html>
)
}
jsx
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{/* 布局 UI */}
<main>{children}</main>
</body>
</html>
)
}
嵌套布局
默认情况下,文件夹层次结构中的布局是嵌套的,这意味着它们通过 children
属性包裹子布局。你可以通过在特定路由段(文件夹)内添加 layout.js
来嵌套布局。
例如,要为 /dashboard
路由创建布局,请在 dashboard
文件夹中添加一个新的 layout.js
文件:
tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return <section>{children}</section>
}
jsx
export default function DashboardLayout({ children }) {
return <section>{children}</section>
}
如果你将上面的两个布局组合起来,根布局(app/layout.js
)将包裹仪表板布局(app/dashboard/layout.js
),后者将包裹 app/dashboard/*
内的路由段。
两个布局将如下嵌套:
提示:
- 布局可以使用
.js
、.jsx
或.tsx
文件扩展名。- 只有根布局可以包含
<html>
和<body>
标签。- 当在同一个文件夹中定义了
layout.js
和page.js
文件时,布局将包裹页面。- 布局默认是服务器组件,但可以设置为客户端组件。
- 布局可以获取数据。有关更多信息,请查看数据获取部分。
- 父布局和其子布局之间无法传递数据。但是,你可以在一个路由中多次获取相同的数据,React 将自动删除重复请求,不会影响性能。
- 布局无法访问
pathname
(了解更多)。但是,导入的客户端组件可以使用usePathname
钩子访问路径名。- 布局无法访问其下面的路由段。要访问所有路由段,你可以在客户端组件中使用
useSelectedLayoutSegment
或useSelectedLayoutSegments
。- 你可以使用路由组将特定路由段选择性地包含或排除在共享布局之外。
- 你可以使用路由组创建多个根布局。点此查看示例。
- 从
pages
目录迁移: 根布局替代了_app.js
和_document.js
文件。查看迁移指南。
模板
模板与布局类似,都可以包裹子布局或页面。与布局在路由之间持久存在并保持状态不同,模板在导航时为每个子项创建一个新实例。这意味着当用户在共享模板的路由之间导航时,会挂载一个新的子实例,重新创建 DOM 元素,客户端组件中的状态不会保留,并且会重新同步 effects。
在某些情况下,你可能需要这些特定行为,此时模板可能比布局更适合。例如:
- 在导航时重新同步
useEffect
。 - 在导航时重置子客户端组件的状态。
可以通过从 template.js
文件导出默认 React 组件来定义模板。该组件应接受 children
属性。
tsx
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
jsx
export default function Template({ children }) {
return <div>{children}</div>
}
在嵌套方面,template.js
在布局和其子项之间渲染。以下是简化的输出:
jsx
<Layout>
{/* 注意模板有一个唯一的 key */}
<Template key={routeParam}>{children}</Template>
</Layout>
示例
元数据
你可以使用元数据 API修改 <head>
HTML 元素,如 title
和 meta
。
可以通过在 layout.js
或 page.js
文件中导出 metadata
对象或 generateMetadata
函数来定义元数据。
tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
jsx
export const metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
提示:你不应该手动在根布局中添加
<head>
标签,如<title>
和<meta>
。相反,应使用元数据 API,它会自动处理高级需求,如流式传输和去重<head>
元素。
了解更多关于可用元数据选项,请参阅 API 参考。
活动导航链接
你可以使用 usePathname() 钩子来确定导航链接是否处于活动状态。
由于 usePathname()
是一个客户端钩子,你需要将导航链接提取到客户端组件中,然后可以将其导入布局或模板:
tsx
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function NavLinks() {
const pathname = usePathname()
return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
首页
</Link>
<Link className={`link ${pathname === '/about' ? 'active' : ''}`} href="/about">
关于
</Link>
</nav>
)
}
jsx
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
首页
</Link>
<Link className={`link ${pathname === '/about' ? 'active' : ''}`} href="/about">
关于
</Link>
</nav>
)
}
tsx
import { NavLinks } from '@/app/ui/nav-links'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}
jsx
import { NavLinks } from '@/app/ui/nav-links'
export default function Layout({ children }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}