Skip to content

布局和模板

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

布局

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

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

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

layout.js特殊文件

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目录的顶层定义,并适用于所有路由。此布局是必需的,并且必须包含htmlbody标签,允许你修改服务器返回的初始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.jspage.js文件时,布局将包装页面。
  • 布局默认是服务器组件,但可以设置为客户端组件
  • 布局可以获取数据。查看数据获取部分以获取更多信息。
  • 父布局与其子布局之间无法传递数据。然而,您可以在路由中多次获取相同的数据,React将自动去重请求而不影响性能。
  • 布局无法访问pathname了解更多)。但是,导入的客户端组件可以使用usePathname钩子访问路径名。
  • 布局无法访问其下方的路由段。要访问所有路由段,您可以在客户端组件中使用useSelectedLayoutSegmentuseSelectedLayoutSegments
  • 您可以使用路由组将特定路由段选择性地包含或排除在共享布局中。
  • 您可以使用路由组创建多个根布局。在这里查看示例
  • **从pages目录迁移:**根布局取代了_app.js_document.js文件。查看迁移指南

模板

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

可能存在需要这些特定行为的情况,而模板会比布局更合适。例如:

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

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

template.js 特殊文件

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>
  {/* 注意模板被赋予了一个唯一的键。 */}
  <Template key={routeParam}>{children}</Template>
</Layout>

Examples

Metadata

您可以使用Metadata APIs修改<head>HTML元素,如titlemeta

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

tsx
import { 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>到根布局中。相反,应使用Metadata 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>
  )
}