我使用的一种方法是将返回的search query JSX 包装在一个备忘录中,或者将其记忆以在更改时更新状态。这是我几个月前建立的一个 subreddit 搜索项目的代码
@/components/Searchbar.tsx
import { FC, useEffect, useMemo, useState } from 'react';
import cn from 'classnames';
import css from './Searchbar.module.css';
import { useRouter } from 'next/router';
import { Input } from '../UI';
import { filterQuery } from '@/lib/helpers';
interface Props {
className?: string;
id?: string;
}
const Searchbar: FC<Props> = ({ className, id = 'r/' }) => {
const router = useRouter();
const [value, setValue] = useState('');
useEffect(() => {
// router.prefetch(url, as)
router.prefetch('/r/[display_name]', `/r/${router.query}`, {
priority: true
});
}, [value]);
return useMemo(
() => (
<div
className={cn(
'relative bg-accents-1 text-base w-full transition-colors duration-150',
className
)}
>
<label className='sr-only' htmlFor={id}>
/r/ - search by subreddit name
</label>
<div className='absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none'>
<span className='text-gray-100 font-semibold sm:text-base'>
/r/
</span>
</div>
<Input
id={id}
name={id}
onChange={setValue}
className={css.input}
defaultValue={
router && router.query ? (router.query.q as string) : ''
}
onKeyUp={e => {
e.preventDefault();
if (e.key === 'Enter') {
const q = e.currentTarget.value;
router.push(
{
pathname: `/r/${q}`,
query: q ? filterQuery({ q }) : {}
},
undefined,
{ shallow: true }
);
}
}}
/>
<div className={css.iconContainer}>
<svg
className={css.icon}
fill='rgb(229, 231, 235)'
viewBox='0 0 20 20'
>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z'
/>
</svg>
</div>
</div>
),
[]
);
};
export default Searchbar;
对应
@/components/Searchbar.module.css文件
.input {
@apply bg-redditSearch px-3 pl-7 py-2 appearance-none w-full transition duration-150 ease-in-out pr-10 text-gray-100 font-semibold;
@screen sm {
min-width: 300px;
@apply text-lg;
}
@screen md {
min-width: 600px;
@apply text-lg;
}
}
.input:focus {
@apply outline-none text-gray-100;
}
.iconContainer {
@apply absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none;
}
.icon {
@apply h-5 w-5;
}
我在 useEffect 挂钩中预取目标路由,以增强生产环境中的用户体验;它也会增强开发中的用户体验,但预取目前仅适用于产品。
然后,有一个@/pages/api 路由处理用户输入的值,如下所示:
@/pages/api/snoosearch.ts
import { Subreddit, Listing } from 'snoowrap';
import { NextApiRequest, NextApiResponse } from 'next';
import { r } from '@/lib/snoo-config';
export type SearchSubreddits = {
subreddit: Listing<Subreddit> | never[];
found: boolean;
};
export default async function (
req: NextApiRequest,
res: NextApiResponse<SearchSubreddits>
) {
const { q } = req.query;
console.log(q);
const data = q
? await r.searchSubreddits({
query: (q as string) ?? 'snowboarding',
count: 10,
limit: 3
})
: [];
res.statusCode = 200;
res.setHeader(
'Cache-Control',
'public, s-maxage=1200, stale-while-revalidate=600'
);
return res.status(200).json({
subreddit: data,
found: true
});
};
因此,这个 lambda 注入一个动态子目录的 getStaticPaths 来处理实时静态路径生成(以及如果使用国际化路由,还可以选择本地化)并通过 ISR 填充任何给定子目录的内容。
如果您不得不在 typescript 环境中使用 Jest 在 Nextjs 中编写测试,您可能熟悉必须通过在测试环境中复制下一个路由器来模拟它。这帮助我了解了很多关于路由器内部的知识:
TLDR -- 提交用户搜索的搜索栏的记忆更新状态,这会强制进行非浅层渲染,这应该可以解决您的问题
模拟 Nextjs 路由器
// Mocks useRouter
type PrefetchOptions = {
priority?: boolean;
locale?: string | false;
};
const useRouter = jest.spyOn(
require('next/router'),
'useRouter'
);
/**
* mockNextUseRouter
* Mocks the useRouter React hook from Next.js on a test-case by test-case basis
*/
export function mockNextUseRouter(props: {
route: string;
prefetch(
url: string,
asPath?: string,
options?: PrefetchOptions
): Promise<void>;
pathname: string;
query: string;
asPath: string;
}) {
useRouter.mockImplementationOnce(() => ({
route: props.route,
prefetch: props.prefetch,
pathname: props.pathname,
query: props.query,
asPath: props.asPath
}));
}