Nextjs如何简单实现搜索功能

·2455·6 分钟·
AI摘要: 本文详细讲解了如何在Next.js中实现搜索功能,包括前端的防抖设计和后端的数据库查询方案。前端通过自定义hook `useDebounce` 实现输入延迟触发搜索请求,避免频繁请求;后端初期采用Prisma直接对数据库进行模糊匹配查询,并指出未来可扩展为Elasticsearch以应对高并发场景。

最近两天在做一个创业项目,目前已经有了100个注册用户,大喜~

项目的大部分代码是AI实现的,自己更多作为产品经理+bug纠错师,确保AI不会在自己的无限生成中迷失自我。好处是上线速度很快,但是缺点是很多代码自己没有真正学会,日后可能会造成麻烦,比如搜索功能。

防抖设计

前端实现实时搜索是比较容易的。通过设置状态

const [query, setQuery] = useState("") // 来保存输入框的实时状态
useEffect(()=> {
    performSearch(query); // 如果query发生变化,则向后端发送搜索请求
}, [query])

但是,如果要实现防抖(延迟3s发送请求),那么useEffect的触发条件就不能是query, 而需要想办法重新设置一个延迟对象:

const [query, setQuery] = useStat("") // 保存实时搜索关键词

function useDebounce<T>(value: T, delay?: number): T{
    const [debouunceValue, setDebounceValue] = useState<T>(value) // 延迟对象
    useEffect(()=>{
        const timer = setTimeout(() => setDebounceValue(value), delay || 500);
        
        return () => {
            clearTimeout(timer)
        }
    }, [value, delay]); // 只要value变化,则debounce变化
    
    return debounceValue;
}

const debouncedQuery = useDebounce(query, 500);

useEffect(()=>{
    performSearch(debounceQuery);
}, [debouncedQuery])

通过引入新的状态debounceValue, 其依赖query, 但是相对query有500ms的延迟才发生变化.

举个例子:

  1. 用户在0ms输入a,那么计时器预计在500ms开始搜索a
  2. 用户在300ms输入了b,那么此时会清除上一个计时器(clearTimeout(timer)), 并创建新的计时器,预计800ms开始搜索b

即:debounceValue永远相对于query延迟500ms发生变化

后端搜索

在资源量特别少的情况下,可以直接对数据库查询,使用in进行关键词搜索

export const findResourcesByKeyword = cache(
    async (keyword: string, limit: number = 10) => {
        const searchTerm = `%${keyword}%`;

        return prisma.resources.findMany({
            where: {
                OR: [
                    {
                        title: {
                            contains: keyword,
                            mode: 'insensitive'
                        }
                    },
                    {
                        description: {
                            contains: keyword,
                            mode: 'insensitive'
                        }
                    },
                    {
                        subject: {
                            contains: keyword,
                            mode: 'insensitive'
                        }
                    },
                    {
                        category: {
                            contains: keyword,
                            mode: 'insensitive'
                        }
                    }
                ]
            },
            select: resourceSelect,
            orderBy: [
                { created_at: 'desc' }
            ],
            take: limit
        });
    });

如果后续资源量上升,并发上升,这个函数需要替换为专业的搜索中间件elasticSearch