跳到主要内容

页面与布局

Pages 路由基于页面的概念构建了一个文件系统路由器。

当你在 pages 目录中添加文件时,它会自动成为一个可访问的路由。

在 Next.js 中,页面是从 pages 目录中的 .js.jsx.ts.tsx 文件导出的 React 组件。每个页面都会根据其文件名关联到一个路由。

示例:如果你创建 pages/about.js 并导出一个如下所示的 React 组件,它将可以通过 /about 访问。

export default function About() {
return <div>About</div>
}

索引路由

路由器会自动将名为 index 的文件路由到目录的根路径。

  • pages/index.js/
  • pages/blog/index.js/blog

嵌套路由

路由器支持嵌套文件。如果你创建嵌套的文件夹结构,文件仍然会以相同的方式自动路由。

  • pages/blog/first-post.js/blog/first-post
  • pages/dashboard/settings/username.js/dashboard/settings/username

动态路由页面

Next.js 支持动态路由页面。例如,如果你创建一个名为 pages/posts/[id].js 的文件,那么它可以通过 posts/1posts/2 等访问。

要了解更多关于动态路由的信息,请查看动态路由文档

布局模式

React 模型允许我们将页面解构为一系列组件。这些组件中的许多通常会在页面之间重用。例如,你可能在每个页面上都有相同的导航栏和页脚。

components/layout.js
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}

示例

使用自定义 App 创建单个共享布局

如果你的整个应用只有一个布局,可以创建一个自定义 App 并用布局包裹你的应用。由于在切换页面时 <Layout /> 组件会被重用,因此其组件状态将被保留(例如输入框的值)。

pages/_app.js
import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}

为每个页面定义布局

如果你需要多个布局,可以向页面添加一个 getLayout 属性,允许你返回一个 React 组件作为布局。这使你能够_为每个页面单独_定义布局。由于我们返回的是一个函数,如果需要的话,我们可以实现复杂的嵌套布局。

pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
return (
/** 你的内容 */
)
}

Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
pages/_app.js
export default function MyApp({ Component, pageProps }) {
// 使用页面级别定义的布局(如果可用)
const getLayout = Component.getLayout ?? ((page) => page)

return getLayout(<Component {...pageProps} />)
}

在页面之间导航时,我们希望 持久化 页面状态(输入框的值、滚动位置等),以获得单页应用(SPA)的体验。

这种布局模式实现了状态持久化,因为 React 组件树在页面转换之间得以保持。借助组件树,React 可以理解哪些元素发生了变化以保留状态。

提示:这个过程称为 reconciliation(协调),这是 React 理解哪些元素发生变化的方式。

在 TypeScript 中使用

使用 TypeScript 时,你必须首先为包含 getLayout 函数的页面创建一个新类型。然后,你必须为 AppProps 创建一个新类型,该类型覆盖 Component 属性以使用之前创建的类型。

pages/index.tsx
import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { NextPageWithLayout } from './_app'

const Page: NextPageWithLayout = () => {
return <p>hello world</p>
}

Page.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}

export default Page
pages/_app.tsx
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}

type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}

export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// 使用页面级别定义的布局(如果可用)
const getLayout = Component.getLayout ?? ((page) => page)

return getLayout(<Component {...pageProps} />)
}

数据获取

在布局内部,你可以使用 useEffect 或像 SWR 这样的库在客户端获取数据。由于这个文件不是页面,目前你不能使用 getStaticPropsgetServerSideProps

components/layout.js
import useSWR from 'swr'
import Navbar from './navbar'
import Footer from './footer'

export default function Layout({ children }) {
const { data, error } = useSWR('/api/navigation', fetcher)

if (error) return <div>加载失败</div>
if (!data) return <div>加载中...</div>

return (
<>
<Navbar links={data.links} />
<main>{children}</main>
<Footer />
</>
)
}