页面与布局
Pages Router 采用基于文件系统的路由机制,其核心概念是页面。
当文件被添加到 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/1
、posts/2
等路径访问。
要了解更多关于动态路由的信息,请查阅 动态路由文档。
布局模式
React 模型允许我们将 页面 解构为一系列组件。其中许多组件通常会在页面间复用。例如,您可能在每个页面上使用相同的导航栏和页脚。
import Navbar from './navbar'
import Footer from './footer'
export default function Layout({ children }) {
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}
示例
使用自定义应用实现单一共享布局
如果整个应用只需一个布局,您可以创建 自定义应用 并用布局组件包裹应用。由于 <Layout />
组件在页面切换时会被复用,其组件状态(如表单输入值)将得以保留。
import Layout from '../components/layout'
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
每页面独立布局
如需多种布局,您可以在页面中添加 getLayout
属性,该属性可返回布局的 React 组件。这允许您实现 按页面定义布局。由于我们返回的是函数,因此还可以实现复杂的嵌套布局。
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>
)
}
export default function MyApp({ Component, pageProps }) {
// 优先使用页面级定义的布局(如果存在)
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
在页面间导航时,为了实现单页应用 (SPA) 体验,我们需要 保持 页面状态(如输入值、滚动位置等)。
这种布局模式能实现状态保持,因为 React 组件树在页面切换时会被保留。通过组件树,React 可以识别哪些元素发生了变化以维持状态。
须知:此过程称为 协调,是 React 识别元素变化的机制。
TypeScript 实现
使用 TypeScript 时,首先需要为包含 getLayout
函数的页面创建新类型,然后为 AppProps
创建覆盖 Component
属性的新类型。
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
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
const Page = () => {
return <p>hello world</p>
}
Page.getLayout = function getLayout(page) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
export default Page
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} />)
}
export default function MyApp({ Component, pageProps }) {
// 优先使用页面级定义的布局(如果存在)
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
数据获取
在布局组件中,您可以使用 useEffect
或 SWR 等库在客户端获取数据。由于该文件不是 页面,当前无法使用 getStaticProps
或 getServerSideProps
。
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 />
</>
)
}