Next.js 中使用 TypeScript 和 HOC 的持久布局

amo*_*ian 3 typescript reactjs higher-order-components next.js

我想向 Next.js 应用程序的某些页面添加持久布局。我发现这篇文章解释了人们如何做到这一点的几种方法。看起来很简单,但是在使用推荐的方法时我遇到了以下两个问题:

  1. 我正在使用 TypeScript,但不确定如何输入。例如,我有以下工作,但我显然不喜欢使用as any
const getLayout =
    (Component as any).getLayout ||
    ((page: NextPage) => <SiteLayout children={page} />);
Run Code Online (Sandbox Code Playgroud)
  1. 我正在使用 Apollo,因此我正在为某些页面使用withApolloHOC(来自此处)。使用这个会导致Component.getLayout总是undefined。我对正在发生的事情没有足够的了解来知道为什么会发生这种情况(我可以猜到),所以我自己很难解决这个问题。

小智 10

我有类似的问题,这就是我为我的项目解决它的方法。

创建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
}
Run Code Online (Sandbox Code Playgroud)

在您的_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
Run Code Online (Sandbox Code Playgroud)

以上只是最适合我的用例的示例实现,您可以根据types/page.d.ts需要切换类型。


tym*_*zap 6

您可以通过添加getLayout属性来扩展 Next 类型定义AppProps.Component

next.d.ts(自下一个 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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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
}

Run Code Online (Sandbox Code Playgroud)

如果您想要更多的类型安全性,您可以定义自定义NextPageWithLayout类型,该类型将断言您的组件已getLayout设置属性。

next.d.ts(自下一个 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;
  };
}
Run Code Online (Sandbox Code Playgroud)

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
}

Run Code Online (Sandbox Code Playgroud)

请注意,在扩展 Next 类型定义时,您必须提供准确的类型/接口签名(以及匹配的泛型),否则声明合并将不起作用。不幸的是,这意味着如果库更改 API,则需要调整类型定义。