首页 > 解决方案 > 如何在 Nextjs 中扩展页面。就像 react-router-dom 中的嵌套开关

问题描述

我一直在使用 react 一段时间,我决定尝试 nextjs。我正在构建一个仪表板,它有一个侧边导航,几个页面将使用该侧边栏。我在 reactjs 中所做的是有一个嵌套开关。并定义我的路线。像这样的东西:

<Switch>
      <Route path="/pageOne">
        <PageOne />
      </Route>
      <Route path="/pageTwo">
        <pageTwo />
      </Route>
      
    </Switch>

但是我浏览了很多资源,但似乎没有谈论扩展页面。我正在考虑的另一个选择是在仪表板文件夹中创建一个子文件夹,然后我需要在所有其他页面上导入侧边栏组件,但是这种方法违反了 DRY 原则。

这样做的更好方法是什么?

编辑

这就是我要归档的内容。当我单击侧边栏中的链接时,我只在页面的右侧导航,而侧边栏在整个页面中仍然存在。 仪表板预览

标签: javascriptreactjsnext.js

解决方案


如果您指的是通过将给定值附加到某些子页面“X”来动态生成页面的能力,例如 Switch 提供的行为,那么您应该查看动态路由器上的文档

Next 中的所有内容都是高度可重用的,因为它对很多事情都非常不以为然

如果您想动态生成子子页面或子子子页面,甚至从某些给定的页面子目录,那么您可以使用 [dynamic].tsx 文件名并使用getStaticPaths后跟getStaticPropsOR getServerSideProps(不使用 getStaticPaths)生成静态路径。澄清一下,Next 的架构,当使用 pages 文件(不包括 pages/ * 和 pages/api* 路由)时,您在同一文件中的服务器和客户端上执行。pages/api 路由是无服务器节点环境,可以从default export任何非 pages/ * pages/* 文件的客户端获取。getStaticProps仍然在服务器上执行,因此为您的包大小贡献了 0。也一样getStaticPathsgetStaticProps在动态文件类型 ( [dynamic].tsx [...dynamic-catch-all].tsx, [[...dynamic-catch-all-optional]].tsx)中使用时,您只需要后者

如果您愿意,请查看我的个人资料以获取具有动态路由的一些存储库

老实说,我不得不重新Switch审视包装器在 CRApp 中所取得的成就,因为我已经很久没有使用它了。我已经在 TSX 上使用 Next 一年多了,所以请随意拍摄任何后续内容。

这是一个最近的客户项目的例子,它在构建过程中动态生成了 25 个以上的画廊子页面

components/Gallery/gallery.tsx

import Image, { ImageLoaderProps } from 'next/image';
import { Timestamp, Container } from '@/components/UI/index';
import { parseUrl } from '@/lib/helpers';
import Link from 'next/link';
import { Gallery } from '@/types/booksy/media';

export default function GalleryMapped({
    images,
    images_count,
    images_per_page
}: Gallery) {
    const GalleryImageLoader = ({
        src,
        width,
        quality
    }: ImageLoaderProps) => {
        return `${src}?w=${width}&q=${quality || 75}`;
    };
    return (
        <Container className='max-w-7xl mx-auto m-12 container'>
            <ul
                role='list'
                className='grid grid-cols-1 gap-x-4 gap-y-8 sm:grid-cols-2 sm:gap-x-6 lg:grid-cols-3 max-w-7xl mx-auto'
            >
                <span className='sr-only'>{images_per_page}</span>
                {images
                    .map((photo, i) => {
                        const targetedThumbnail = photo.thumbnails['640,0-hr'].url;
                        // console.log('target: ', targetedThumbnail);
                        const thumbnailDecoded = decodeURI(targetedThumbnail);
                        // console.log('decoded: ', thumbnailDecoded);
                        const fragmentThumbnailURI = parseUrl(thumbnailDecoded);
                        // console.log('URI Fragments: ', fragmentThumbnailURI);
                        const thumbnailReconstructed = `${fragmentThumbnailURI!.baseUrl.concat(
                            fragmentThumbnailURI!.pathname
                        )}`;
                        // console.log('reconstructed: ', thumbnailReconstructed);

                        return (
                            <li key={i++} className='relative'>
                                <div className='group cover block w-full rounded-xl bg-redditBG focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-gray-100 focus-within:ring-indigo-500 overflow-hidden'>
                                    <Link
                                        href='/gallery/[id]'
                                        as={`/gallery/${photo.image_id}`}
                                        passHref
                                        shallow={true}
                                        scroll={true}
                                    >
                                        <a id={`/gallery#${photo.image_id}`}>
                                            <Image
                                                loader={GalleryImageLoader}
                                                src={thumbnailReconstructed ?? photo.image}
                                                width='1280'
                                                height='1294'
                                                alt={`${photo.description ?? photo.category}`}
                                                quality={100}
                                                className='object-cover pointer-events-none group-hover:opacity-75 shadow-cardHover'
                                            />
                                        </a>
                                    </Link>
                                    <button
                                        type='button'
                                        className='absolute inset-0 focus:outline-none'
                                    >
                                        <span className='sr-only'>
                                            View details for {photo.image_id}
                                        </span>
                                    </button>
                                </div>
                                <p className='mt-2 block text-sm font-medium text-olive-300 truncate pointer-events-none'>
                                    <Timestamp timestamp={photo.created} />
                                </p>
                                <p className='block text-sm font-medium text-gray-500 pointer-events-none'>
                                    {`${++i}/${images_count}`}
                                </p>
                            </li>
                        );
                    })
                    .reverse()}
            </ul>
        </Container>
    );
}

然后,它被导入pages/gallery/[id].tsxwheregetStaticPathsgetStaticProps通过 apollo 客户端和 SWR 注入当前传入数据的服务器馈送 props。

pages/Gallery/[id].tsx

import {
    GetStaticPropsContext,
    GetStaticPropsResult,
    GetStaticPathsContext,
    InferGetStaticPropsType
} from 'next';
import { initializeApollo, addApolloState } from '@/lib/apollo';
import {
    DynamicNavDocument,
    DynamicNavQueryVariables,
    DynamicNavQuery,
    WordpressMenuNodeIdTypeEnum
} from '@/graphql/generated/graphql';
import { AppLayout } from '@/components/Layout';
import { useRouter } from 'next/router';
import { Container, Fallback } from '@/components/UI';
import useSWR from 'swr';
import { fetcherGallery } from '@/lib/swr-fetcher';
import { Gallery } from '@/types/booksy/media';
import { Configuration, Fetcher } from 'swr/dist/types';
import {
    getLatestBooksyPhotos,
    getBooksyPhotoById
} from '@/lib/booksy';
import Image, { ImageLoaderProps } from 'next/image';
import { parseUrl } from '@/lib/helpers';

export async function getStaticPaths({
    locales
}: GetStaticPathsContext): Promise<{
    paths: string[];
    fallback: false;
}> {
    const data: Response = await getLatestBooksyPhotos();
    const pathsData: Gallery = await data.json();

    return {
        paths: locales
            ? locales.reduce<string[]>((arr, locale) => {
                    pathsData.images.forEach(imageId => {
                        arr.push(`/${locale}/gallery/${imageId.image_id}`);
                    });
                    return arr;
              }, [])
            : pathsData?.images.map(
                    imageId => `/gallery/${imageId.image_id}`
              ),
        fallback: false
    };
}

export async function getStaticProps<P>(
    ctx: GetStaticPropsContext
): Promise<
    GetStaticPropsResult<
        P & {
            Header: DynamicNavQuery['Header'];
            Footer: DynamicNavQuery['Footer'];
            initDataImage: Partial<
                Configuration<Gallery, any, Fetcher<Gallery>>
            >;
            id: string;
        }
    >
> {
    const apolloClient = initializeApollo({
        headers: ctx.params ?? {}
    });

    await apolloClient.query<
        DynamicNavQuery,
        DynamicNavQueryVariables
    >({
        notifyOnNetworkStatusChange: true,
        fetchPolicy: 'cache-first',
        query: DynamicNavDocument,
        variables: {
            idHead: 'Header',
            idTypeHead: WordpressMenuNodeIdTypeEnum.NAME,
            idTypeFoot: WordpressMenuNodeIdTypeEnum.NAME,
            idFoot: 'Footer'
        }
    });
    const id = ctx?.params ? (ctx.params.id as string) : '21177790';
    const initDataGallery = await getBooksyPhotoById(id);
    const initDataImage: Gallery = await initDataGallery.json();

    return addApolloState(apolloClient, {
        props: {
            initDataImage,
            id
        },
        revalidate: 600
    });
}

export default function GalleryById<
    T extends typeof getStaticProps
>({
    Header,
    Footer,
    initDataImage,
    id
}: InferGetStaticPropsType<T>) {
    const router = useRouter();
    const parsedId = router.query
        ? (router.query.id as string)
        : '';

    const { data } = useSWR<Gallery>(
        `/api/booksy-image-by-id/?category=biz_photo&image_id=${
            id ? id : parsedId
        }`,
        fetcherGallery,
        initDataImage
    );

    const booksyImageLoader = ({
        src,
        width,
        quality
    }: ImageLoaderProps) => {
        return `${src}?w=${width}&q=${quality || 75}`;
    };

    const targetThumbnail = data?.images[0].thumbnails['640,0-hr']
        .url
        ? data.images[0].thumbnails['640,0-hr'].url
        : data?.images[0].image
        ? data.images[0].image
        : '';

    const thumbnailDecoded = decodeURI(targetThumbnail ?? '');

    const fragmentThumbnailURI = parseUrl(thumbnailDecoded ?? '');

    const thumbnailReconstructed =
        `${fragmentThumbnailURI?.baseUrl.concat(
            fragmentThumbnailURI.pathname
        )}
` ?? '';

    return (
        <>
            {router.isFallback ? (
                <Fallback />
            ) : (
                <AppLayout
                    Header={Header}
                    Footer={Footer}
                    title={`${data?.images[0].image_id ?? parsedId}`}
                >
                    <Container
                        clean
                        className='my-16 max-w-5xl mx-auto block rounded-xl shadow-cardHover'
                    >
                        {data && data.images ? (
                            <Image
                                className='rounded-xl object-cover'
                                src={
                                    thumbnailReconstructed !== ''
                                        ? thumbnailReconstructed
                                        : '/doge-404.jpg'
                                }
                                loader={booksyImageLoader}
                                width='1280'
                                height='1294'
                                priority
                                quality={100}
                                layout='responsive'
                            />
                        ) : (
                            <Fallback />
                        )}
                    </Container>
                </AppLayout>
            )}
        </>
    );
}

目录看起来像这样

目前在生产版本中生成了 48 条路径


推荐阅读