javascript - 如何在 Nextjs 中扩展页面。就像 react-router-dom 中的嵌套开关
问题描述
我一直在使用 react 一段时间,我决定尝试 nextjs。我正在构建一个仪表板,它有一个侧边导航,几个页面将使用该侧边栏。我在 reactjs 中所做的是有一个嵌套开关。并定义我的路线。像这样的东西:
<Switch>
<Route path="/pageOne">
<PageOne />
</Route>
<Route path="/pageTwo">
<pageTwo />
</Route>
</Switch>
但是我浏览了很多资源,但似乎没有谈论扩展页面。我正在考虑的另一个选择是在仪表板文件夹中创建一个子文件夹,然后我需要在所有其他页面上导入侧边栏组件,但是这种方法违反了 DRY 原则。
这样做的更好方法是什么?
编辑
这就是我要归档的内容。当我单击侧边栏中的链接时,我只在页面的右侧导航,而侧边栏在整个页面中仍然存在。
解决方案
如果您指的是通过将给定值附加到某些子页面“X”来动态生成页面的能力,例如 Switch 提供的行为,那么您应该查看动态路由器上的文档
Next 中的所有内容都是高度可重用的,因为它对很多事情都非常不以为然
如果您想动态生成子子页面或子子子页面,甚至从某些给定的页面子目录,那么您可以使用 [dynamic].tsx 文件名并使用getStaticPaths
后跟getStaticProps
OR getServerSideProps
(不使用 getStaticPaths)生成静态路径。澄清一下,Next 的架构,当使用 pages 文件(不包括 pages/ * 和 pages/api* 路由)时,您在同一文件中的服务器和客户端上执行。pages/api 路由是无服务器节点环境,可以从default export
任何非 pages/ * pages/* 文件的客户端获取。getStaticProps
仍然在服务器上执行,因此为您的包大小贡献了 0。也一样getStaticPaths
。getStaticProps
在动态文件类型 ( [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].tsx
wheregetStaticPaths
并getStaticProps
通过 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 条路径
推荐阅读
- firebase - Firebase 存储 - 如何授权文件下载?
- javascript - 更新 3 级嵌套数组 mongoose
- deno - Deno 相当于 Reader/Conn 的“窥视”?
- mixed-models - 具有不同结果测量次数的多变量多级模型
- selenium - 使用不同数据库文件的 Flask 应用程序进行 Selenium 测试
- python - 你如何编写一个绘制...2的pygame函数
- python - 如何更有效地组织 Padas Dataframes 中单元格内的元组列表?
- javascript - Shopify 如何使用 Liquid 代码段更新 UI 中的数量
- c++ - 如何将字符串转换为目录条目?
- java - 反思与收藏