使用TypeScript和HOC在Next.js中实现持久布局

cwdobuhd  于 2023-05-06  发布在  TypeScript
关注(0)|答案(5)|浏览(210)

我想在我的Next.js应用程序的某些页面上添加一个持久化布局。我发现this article解释了几种如何做到这一点的方法。这看起来很简单,但是我在使用推荐的方法时遇到了以下两个问题:
1.我正在使用TypeScript,不知道如何输入。例如,我有下面的代码,它可以工作,但我显然不喜欢使用as any

const getLayout =
    (Component as any).getLayout ||
    ((page: NextPage) => <SiteLayout children={page} />);

1.我正在使用Apollo,因此我在某些页面上使用withApollo HOC(来自here)。使用此选项会导致Component.getLayout始终为undefined。我没有足够好的理解正在发生的事情,知道为什么会发生这种情况(我可以猜到),所以很难自己解决这个问题。

自从提出这个问题后,他们在文档中添加了一个很好的示例

odopli94

odopli941#

我有类似的问题,这是我如何解决我的项目。
创建types/page.d.ts类型定义:

import { NextPage } from 'next'
import { ComponentType, ReactElement, ReactNode } from 'react'

export type Page<P = {}> = NextPage<P> & {
  // You can disable whichever you don't need
  getLayout?: (page: ReactElement) => ReactNode
  layout?: ComponentType
}

_app.tsx文件中,

import type { AppProps } from 'next/app'
import { Fragment } from 'react'
import type { Page } from '../types/page'

// this should give a better typing
type Props = AppProps & {
  Component: Page
}
const MyApp = ({ Component, pageProps }: Props) => {
  // adjust accordingly if you disabled a layout rendering option
  const getLayout = Component.getLayout ?? (page => page)
  const Layout = Component.layout ?? Fragment

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

  // or swap the layout rendering priority
  // return getLayout(<Layout><Component {...pageProps} /></Layout>)
}

export default MyApp

上面只是一个最适合我的用例的示例实现,您可以切换types/page.d.ts中的类型以满足您的需求。

vh0rcniy

vh0rcniy2#

可以通过向AppProps.Component添加getLayout属性来扩展Next类型定义。
next.d.ts(从Next 11.1.0开始):

import type { CompletePrivateRouteInfo } from 'next/dist/shared/lib/router/router';
import type { Router } from 'next/dist/client/router';

declare module 'next/app' {
  export declare type AppProps = Pick<CompletePrivateRouteInfo, 'Component' | 'err'> & {
    router: Router;
  } & Record<string, any> & {
    Component: {
      getLayout?: (page: JSX.Element) => JSX.Element;
    }
  }
}

pages/_app.tsx

import type { AppProps } from 'next/app';

const NextApp = ({ Component, pageProps }: AppProps) => {
  // Typescript does not complain about unknown property
  const getLayout = Component.getLayout || ((page) => page);
  // snip
}

如果你想有更多的类型安全,你可以定义自定义的NextPageWithLayout类型,它将Assert你的组件有getLayout属性集。
next.d.ts(从Next 11.1.0开始):

import type { NextPage } from 'next';
import type { NextComponentType } from 'next/dist/next-server/lib/utils';

declare module 'next' {
  export declare type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
    getLayout: (component: NextComponentType) => JSX.Element;
  };
}

pages/example-page/index.tsx

import type { NextPageWithLayout } from 'next';

const ExamplePage: NextPageWithLayout<ExamplePageProps> = () => {
  // snip
}

// If you won't define `getLayout` property on this component, TypeScript will complain
ExamplePage.getLayout = (page) => {
  // snip
}

请注意,在扩展Next类型定义时,必须提供精确的类型/接口签名(以及匹配的泛型),否则declaration merging将无法工作。不幸的是,这意味着如果库更改API,则需要调整类型定义。

zbsbpyhn

zbsbpyhn3#

1.我相信next.js提供了一个Component prop作为AppProps的一部分。这个例子应该有你要找的东西。
1.在创建使用withApollo Package 的组件后,您可能需要分配getLayout

import CustomLayout = CustomLayout;
const MyPage = () => (...);

const MyPageWithApollo = withApollo(MyPage);
MyPageWithApollo.Layout = CustomLayout;
export default MyPageWithApollo;

此外,next.js有两个例子可以用来实现这一点(虽然不是用typescript编写的):

  1. Dynamic App Layout
  2. With Apollo
p5fdfcr1

p5fdfcr14#

// if no Layout is defined in page, use this
const Default: FC = ({children}) => <>{children}</>

function MyApp({Component, pageProps}: AppProps & {Component: {Layout: FC}}) {
  // this will make layput flexible. 
  // Layout is extracted from the page
  const Layout = Component.Layout ?? Default
  return (
      <Layout>
        <Component {...pageProps} />
      </Layout>
  )
}
export default MyApp

假设您想在“Home”中使用Layout

import { Layout } from "@components"

export default function Home({
     ...
}
Home.Layout = Layout
wxclj1h5

wxclj1h55#

就这么做!不要惊慌,生活很简单。

import { PropsWithChildren } from "react";
    
export default function Layout({ children }: PropsWithChildren) {
    return <main>{children}</main>
}

相关问题