跳至内容

布局和模板

特殊文件 layout.jstemplate.js 允许你创建在 路由 之间共享的 UI。此页面将指导你了解如何以及何时使用这些特殊文件。

布局

布局是在多个路由之间**共享**的 UI。在导航时,布局保留状态、保持交互性并且不会重新渲染。布局也可以嵌套

你可以通过从 layout.js 文件的默认导出 React 组件来定义布局。该组件应该接受一个 children 道具,该道具将在渲染期间填充子布局(如果存在)或页面。

例如,布局将与 /dashboard/dashboard/settings 页面共享

layout.js special file
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 目录的顶层定义,并应用于所有路由。此布局是**必填**的,并且必须包含 htmlbody 标签,允许你修改从服务器返回的初始 HTML。

app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* Layout UI */}
        <main>{children}</main>
      </body>
    </html>
  )
}

嵌套布局

默认情况下,文件夹层次结构中的布局是**嵌套**的,这意味着它们通过其 children 道具包装子布局。你可以通过在特定路由段(文件夹)中添加 layout.js 来嵌套布局。

例如,要为 /dashboard 路由创建布局,请在 dashboard 文件夹内添加一个新的 layout.js 文件

Nested Layout
app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

如果你要组合以上两个布局,则根布局 (app/layout.js) 将包装仪表盘布局 (app/dashboard/layout.js),后者将包装 app/dashboard/* 内的路由段。

这两个布局将嵌套如下

Nested Layouts

需要知道:

  • 布局可以使用 .js.jsx.tsx 文件扩展名。
  • 只有根布局才能包含 <html><body> 标签。
  • 当在同一个文件夹中定义了 layout.jspage.js 文件时,布局将包装页面。
  • 布局默认情况下是服务器组件,但可以设置为客户端组件
  • 布局可以获取数据。查看数据获取部分以获取更多信息。
  • 无法在父布局与其子级之间传递数据。但是,你可以在路由中多次获取相同的数据,并且 React 将自动对请求进行去重,而不会影响性能。
  • 布局无法访问 pathname了解更多)。但是导入的客户端组件可以使用 usePathname 钩子访问 pathname。
  • 布局无法访问其自身下方的路由段。要访问所有路由段,你可以在客户端组件中使用 useSelectedLayoutSegmentuseSelectedLayoutSegments
  • 你可以使用路由组选择加入或退出共享布局的特定路由段。
  • 你可以使用路由组创建多个根布局。请参阅此处的示例
  • **从 pages 目录迁移:**根布局替换了 _app.js_document.js 文件。查看迁移指南

模板

模板类似于布局,它们都包裹着子布局或页面。与跨路由持久化并保持状态的布局不同,模板在每次导航时都会为其每个子元素创建一个新实例。这意味着当用户在共享模板的路由之间导航时,子元素的新实例会被挂载,DOM 元素会被重新创建,客户端组件中的状态**不会**被保留,并且效果会被重新同步。

在某些情况下,您可能需要这些特定的行为,而模板将比布局更合适。例如

  • 在导航时重新同步useEffect
  • 在导航时重置子客户端组件的状态。

模板可以通过从template.js文件中导出默认的 React 组件来定义。该组件应该接受一个children prop。

template.js special file
app/template.tsx
export default function Template({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}

在嵌套方面,template.js渲染在布局及其子元素之间。这是一个简化的输出

输出
<Layout>
  {/* Note that the template is given a unique key. */}
  <Template key={routeParam}>{children}</Template>
</Layout>

示例

元数据

您可以使用元数据 API修改<head> HTML 元素,例如titlemeta

元数据可以通过在layout.jspage.js文件中导出metadata对象generateMetadata函数来定义。

app/page.tsx
import type { Metadata } from 'next'
 
export const metadata: 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/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>
  )
}