跳至内容

页面和布局

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

当文件添加到 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 (
    /** Your content */
  )
}
 
Page.getLayout = function getLayout(page) {
  return (
    <Layout>
      <NestedLayout>{page}</NestedLayout>
    </Layout>
  )
}
pages/_app.js
export default function MyApp({ Component, pageProps }) {
  // Use the layout defined at the page level, if available
  const getLayout = Component.getLayout ?? ((page) => page)
 
  return getLayout(<Component {...pageProps} />)
}

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

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

注意:此过程称为协调,这是 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) {
  // Use the layout defined at the page level, if available
  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>Failed to load</div>
  if (!data) return <div>Loading...</div>
 
  return (
    <>
      <Navbar links={data.links} />
      <main>{children}</main>
      <Footer />
    </>
  )
}