现在开始了解Next.js 对更高效的layout布局支持 .. - Layouts RFC

这个React 模型允许我们结构一个页面到一系列组件 ..

许多这些组件能够在多个页面之间进行重用… 举个例子,你可能想要在每个页面上都有相同的导航条以及底部栏 ..

  1. // components/layout.js
  2. import Navbar from './navbar'
  3. import Footer from './footer'
  4. export default function Layout({ children }) {
  5. return (
  6. <>
  7. <Navbar />
  8. <main>{children}</main>
  9. <Footer />
  10. </>
  11. )
  12. }

examples

自定义App的单个共享Layout

如果在整个应用中仅仅只有一个布局,我们能够创建一个自定义的App并通过layout包装应用 .. 当页面发生改变时重用<Layout />,它的组件状态仍然能够获得保留 ..

  1. // pages/_app.js
  2. import Layout from '../components/layout'
  3. export default function MyApp({ Component, pageProps }) {
  4. return (
  5. <Layout>
  6. <Component {...pageProps} />
  7. </Layout>
  8. )
  9. }

per-Page Layouts

如果存在多个布局,能够增加一个属性到页面上(getLayout),允许你针对layout 返回一个React 组件,这允许你定义一个layout - 基于每一个页面,因此我们能够返回一个函数,用于内嵌一个我们想要的复杂的布局 …

  1. // pages/index.js
  2. import Layout from '../components/layout'
  3. import NestedLayout from '../components/nested-layout'
  4. export default function Page() {
  5. return {
  6. /** Your content */
  7. }
  8. }
  9. Page.getLayout = function getLayout(page) {
  10. return (
  11. <Layout>
  12. <NestedLayout>{page}</NestedLayout>
  13. </Layout>
  14. )
  15. }

然后在pages/_app.js

  1. // pages/_app.js
  2. export default function MyApp({ Component, pageProps }) {
  3. // Use the layout defined at the page level, if available
  4. const getLayout = Component.getLayout || ((page) => page)
  5. return getLayout(<Component {...pageProps} />)
  6. }

当在多个页面进行导航的时候,我们想要持久化页面状态(输入值,滚动位置,以及其他),增加单页面应用的体验 ..

这个layout 模式能够启用状态持久化(因为React 组件树在页面过渡之间仍然是维持的,保留的),使用组件树,React 可以了解哪些元素已更改以保持状态。

这个过程叫做 reconciliation ,它表示React 如何理解那些元素发生了改变 。。

with TypeScript

当使用Ts的时候,你必须为页面创建一个新类型(包含getLayout函数的类型),然后创建一个新的AppProps类型,它会覆盖Component属性去使用之前创建的类型 …

假设一个页面是:

  1. // pages/index.tsx
  2. import type { ReactElement } from 'react'
  3. import Layout from '../components/layout'
  4. import NestedLayout from '../components/nested-layout'
  5. import type { NextPageWithLayout } from './_app'
  6. const Page: NextPageWithLayout = () => {
  7. return <p>hello world</p>
  8. }
  9. Page.getLayout = function getLayout(page: ReactElement) {
  10. return (
  11. <Layout>
  12. <NestedLayout>{page}</NestedLayout>
  13. </Layout>
  14. )
  15. }
  16. export default Page

然后对于布局,我们现在的类型就是

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

然后当前pages/_app.js的属性就应该是

  1. type AppPropsWithLayout = AppProps & {
  2. Component: NextPageWithLayout
  3. }

最终组件渲染就变成了

  1. // pages/_app.tsx
  2. import type { ReactElement, ReactNode } from 'react'
  3. import type { NextPage } from 'next'
  4. import type { AppProps } from 'next/app'
  5. export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
  6. getLayout?: (page: ReactElement) => ReactNode
  7. }
  8. type AppPropsWithLayout = AppProps & {
  9. Component: NextPageWithLayout
  10. }
  11. export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
  12. // Use the layout defined at the page level, if available
  13. const getLayout = Component.getLayout ?? ((page) => page)
  14. return getLayout(<Component {...pageProps} />)
  15. }

数据抓取

在layout中,你能够通过useEffect,swr进行客户端数据抓取 ,因为这个文件不是一个页面(Page),你不能够使用getStaticProps,getServerSideProps

  1. // components/layout.js
  2. import useSWR from 'swr'
  3. import Navbar from './navbar'
  4. import Footer from './footer'
  5. export default function Layout({ children }) {
  6. const { data, error } = useSWR('/api/navigation', fetcher)
  7. if (error) return <div>Failed to load</div>
  8. if (!data) return <div>Loading...</div>
  9. return (
  10. <>
  11. <Navbar links={data.links} />
  12. <main>{children}</main>
  13. <Footer />
  14. </>
  15. )
  16. }