外观
布局和模板
布局(layouts)和模板(templates)
特殊文件layout.js
和template.js
允许您创建在多个路由之间共享的UI。本页将指导您如何以及何时使用这些特殊文件。
布局(layouts)
布局是在多个路由之间共享的UI。在导航时,布局保持状态,保持交互性,并且不会重新渲染。布局还可以嵌套。
您可以通过从layout.js
文件中默认导出一个React组件来定义布局。该组件应接受一个children
属性,在渲染过程中将填充子布局(如果存在)或页面。
例如,这个布局将在/dashboard
和/dashboard/settings
页面之间共享:
app/dashboard/layout.tsx
export default function DashboardLayout({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) {
return (
<section>
{/* Include shared UI here e.g. a header or sidebar */}
<nav></nav>
{children}
</section>
)
}
app/dashboard/layout.js
export default function DashboardLayout({
children, // will be a page or nested layout
}) {
return (
<section>
{/* Include shared UI here e.g. a header or sidebar */}
<nav></nav>
{children}
</section>
)
}
根布局(Root Layout)
根布局定义在app
目录的顶层,适用于所有路由。这个布局是必需的,并且必须包含html
和body
标签,允许您修改从服务器返回的初始HTML。
app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{/* Layout UI */}
<main>{children}</main>
</body>
</html>
)
}
app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{/* Layout UI */}
<main>{children}</main>
</body>
</html>
)
}
嵌套布局
默认情况下,文件夹层次结构中的布局是嵌套的,这意味着它们通过children
属性包装子布局。您可以通过在特定路由段(文件夹)内添加layout.js
来嵌套布局。
例如,要为/dashboard
路由创建一个布局,在dashboard
文件夹内添加一个新的layout.js
文件:
app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
app/dashboard/layout.js
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
钩子访问pathname。 - 布局无法访问其下方的路由段。要访问所有路由段,您可以在客户端组件中使用
useSelectedLayoutSegment
或useSelectedLayoutSegments
。 - 您可以使用路由组来选择性地将特定路由段包含在共享布局中或排除在外。
- 您可以使用路由组创建多个根布局。查看此处的示例。
- 从
pages
目录迁移: 根布局替代了_app.js
和_document.js
文件。查看迁移指南。
模板(templates)
模板类似于布局,它们包装子布局或页面。与在路由之间保持状态的布局不同,模板在导航时为其每个子组件创建一个新实例。这意味着当用户在共享模板的路由之间导航时,子组件的新实例会被挂载,DOM元素会被重新创建,客户端组件中的状态不会被保留,效果会重新同步。
在某些情况下,您可能需要这些特定行为,模板可能比布局更适合。例如:
- 在导航时重新同步
useEffect
。 - 在导航时重置子客户端组件的状态。
可以通过从template.js
文件中导出默认的React组件来定义模板。该组件应接受一个children
属性。
app/template.tsx
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
app/template.js
export default function Template({ children }) {
return <div>{children}</div>
}
在嵌套方面,template.js
在布局和其子组件之间渲染。这里是一个简化的输出:
Output
<Layout>
{/* 注意模板被赋予了一个唯一的key。 */}
<Template key={routeParam}>{children}</Template>
</Layout>
示例
元数据(metadata)
您可以使用元数据API修改<head>
HTML元素,如title
和meta
。
可以通过在layout.js
或page.js
文件中导出metadata
对象或generateMetadata
函数来定义元数据。
app/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
app/page.js
export const metadata = {
title: 'Next.js',
}
export default function Page() {
return '...'
}
提示
您不应手动将<head>
标签(如<title>
和<meta>
)添加到根布局中。相反,使用元数据API,它会自动处理诸如流式传输和去重<head>
元素等高级需求。
在API参考中了解更多可用的元数据选项。
活动导航链接
您可以使用usePathname()钩子来确定导航链接是否处于活动状态。
由于usePathname()
是一个客户端钩子,您需要将导航链接提取到一个客户端组件中,该组件可以导入到您的布局或模板中:
app/ui/nav-links.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="/">
Home
</Link>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}
app/ui/nav-links.js
'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="/">
Home
</Link>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}
app/layout.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>
)
}
app/layout.js
import { NavLinks } from '@/app/ui/nav-links'
export default function Layout({ children }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}