Next.js 使用 TypeScript 和 HOC 的持久布局
Persistent layout in Next.js with TypeScript and HOC
我想为我的 Next.js 应用程序的某些页面添加持久布局。我发现 this article 解释了一些关于如何做到这一点的方法。看起来很简单,但是我在使用推荐的方法时遇到了以下两个问题:
- 我正在使用 TypeScript,但不确定如何输入。例如,我有以下工作,但我显然不喜欢使用
as any
:
const getLayout =
(Component as any).getLayout ||
((page: NextPage) => <SiteLayout children={page} />);
- 我正在使用 Apollo,所以我在某些页面上使用了
withApollo
HOC(来自 here)。使用它会导致 Component.getLayout
始终为 undefined
。我对正在发生的事情没有足够的了解,不知道为什么会这样(我猜),所以很难自己解决这个问题。
- 我相信 next.js 提供了一个
Component
道具作为 AppProps 的一部分。 This example 应该有你要找的东西。
- 您可能需要在创建用
withApollo
包装的组件后分配 getLayout
:
import CustomLayout = CustomLayout;
const MyPage = () => (...);
const MyPageWithApollo = withApollo(MyPage);
MyPageWithApollo.Layout = CustomLayout;
export default MyPageWithApollo;
此外,next.js 有两个示例可以用来实现这一点(虽然不是用打字稿写的):
我有类似的问题,这就是我为我的项目解决的方法。
创建一个 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
中切换类型以满足您的需要。
您可以通过将 getLayout
属性 添加到 AppProps.Component
.
来扩展 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
类型,这将断言你的组件已设置 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 类型定义时,您必须提供准确的 type/interface 签名(连同匹配的泛型),否则 declaration merging 将无法工作。不幸的是,这意味着如果库更改 API.
则需要调整类型定义
我想为我的 Next.js 应用程序的某些页面添加持久布局。我发现 this article 解释了一些关于如何做到这一点的方法。看起来很简单,但是我在使用推荐的方法时遇到了以下两个问题:
- 我正在使用 TypeScript,但不确定如何输入。例如,我有以下工作,但我显然不喜欢使用
as any
:
const getLayout =
(Component as any).getLayout ||
((page: NextPage) => <SiteLayout children={page} />);
- 我正在使用 Apollo,所以我在某些页面上使用了
withApollo
HOC(来自 here)。使用它会导致Component.getLayout
始终为undefined
。我对正在发生的事情没有足够的了解,不知道为什么会这样(我猜),所以很难自己解决这个问题。
- 我相信 next.js 提供了一个
Component
道具作为 AppProps 的一部分。 This example 应该有你要找的东西。 - 您可能需要在创建用
withApollo
包装的组件后分配getLayout
:
import CustomLayout = CustomLayout;
const MyPage = () => (...);
const MyPageWithApollo = withApollo(MyPage);
MyPageWithApollo.Layout = CustomLayout;
export default MyPageWithApollo;
此外,next.js 有两个示例可以用来实现这一点(虽然不是用打字稿写的):
我有类似的问题,这就是我为我的项目解决的方法。
创建一个 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
中切换类型以满足您的需要。
您可以通过将 getLayout
属性 添加到 AppProps.Component
.
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
类型,这将断言你的组件已设置 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 类型定义时,您必须提供准确的 type/interface 签名(连同匹配的泛型),否则 declaration merging 将无法工作。不幸的是,这意味着如果库更改 API.
则需要调整类型定义