From 17c8a9f2ea59efc3accb385fe280c61c0a57c7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E5=AE=87=E9=98=B3?= Date: Fri, 24 Jul 2026 16:07:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E7=9B=B8=E5=85=B3API=E5=92=8C=E7=BB=84=E4=BB=B6=E4=BB=A5?= =?UTF-8?q?=E5=A2=9E=E5=BC=BA=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修改getArticlePagingAPI以支持按标题模糊搜索,更新相关参数名称。 2. 优化搜索组件逻辑,使用防抖机制处理关键词变化,避免过多请求。 3. 更新CommentForm组件的按钮样式,提升用户交互体验。 4. 重构useDebounce钩子,确保最新的函数和等待时间被正确引用。 --- src/api/article.ts | 4 +- src/app/article/components/Comment/index.tsx | 2 +- src/components/Search/index.tsx | 65 ++++++++++++-------- src/hooks/useDebounce.tsx | 43 +++++++++---- src/lib/article.ts | 8 +-- 5 files changed, 76 insertions(+), 46 deletions(-) diff --git a/src/api/article.ts b/src/api/article.ts index 454d748..3815f6d 100755 --- a/src/api/article.ts +++ b/src/api/article.ts @@ -6,8 +6,8 @@ export const getArticleDataAPI = async (id: number, password?: string) => { return await Request
('GET', `/article${!password ? `/${id}` : `/${id}?password=${password}`}`); } -// 获取文章列表 -export const getArticlePagingAPI = async (params?: Page & { key?: string }) => { +// 获取文章列表(title 按标题模糊搜索) +export const getArticlePagingAPI = async (params?: Page & { title?: string }) => { return await Request>('GET', `/article`, { params: params ?? {} }); diff --git a/src/app/article/components/Comment/index.tsx b/src/app/article/components/Comment/index.tsx index 5d18d16..d40f6e5 100755 --- a/src/app/article/components/Comment/index.tsx +++ b/src/app/article/components/Comment/index.tsx @@ -242,7 +242,7 @@ const CommentForm = ({ articleId }: Props) => { ) : ( - )} diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx index a587d15..efdea55 100755 --- a/src/components/Search/index.tsx +++ b/src/components/Search/index.tsx @@ -5,7 +5,6 @@ import Link from 'next/link'; import { Modal, TextField, type DisclosureProps } from '@/ThriveUI'; import { getArticlePagingAPI } from '@/api/article'; import { Article } from '@/types/app/article'; -import useDebounce from '@/hooks/useDebounce'; import Empty from '../Empty'; interface Props { @@ -18,29 +17,6 @@ export default ({ disclosure }: Props) => { const [data, setData] = useState>(); const [searchKey, setSearchKey] = useState(''); - const getArticleList = async (key: string) => { - if (key.trim().length === 0) { - setData(undefined); - return; - } - - const { data } = await getArticlePagingAPI({ - key, - pageNum: 1, - pageSize: 10, - }); - - setData(data); - }; - - const debouncedFetchArticles = useDebounce(getArticleList, 300); - - const onSearchArticle = (e: React.ChangeEvent) => { - const key = e.target.value; - setSearchKey(key); - debouncedFetchArticles(key); - }; - useEffect(() => { if (!isOpen) { setData(undefined); @@ -48,14 +24,49 @@ export default ({ disclosure }: Props) => { } }, [isOpen]); + // 关键词变化时防抖请求;清理函数取消过期请求,避免清空后再搜被旧响应覆盖 + useEffect(() => { + if (!isOpen) return; + + const key = searchKey.trim(); + if (!key) { + setData(undefined); + return; + } + + let cancelled = false; + const timer = window.setTimeout(async () => { + try { + const { data: result } = await getArticlePagingAPI({ + title: key, + pageNum: 1, + pageSize: 10, + }); + if (!cancelled) setData(result); + } catch { + if (!cancelled) setData(undefined); + } + }, 300); + + return () => { + cancelled = true; + clearTimeout(timer); + }; + }, [searchKey, isOpen]); + return (
- + setSearchKey(e.target.value)} + />
- {data?.result - ? data?.result?.map((item) => ( + {data?.result?.length + ? data.result.map((item) => ( any>(func: T, wait: number) { - const timeoutRef = useRef(null); +/** 返回稳定的防抖函数;始终调用最新的 func,避免闭包过期 */ +export default function useDebounce unknown>(func: T, wait: number) { const funcRef = useRef(func); - funcRef.current = func; + const waitRef = useRef(wait); + const timeoutRef = useRef(undefined); - return (...args: Parameters) => { - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - timeoutRef.current = window.setTimeout(() => { - funcRef.current(...args); - }, wait); - }; + useEffect(() => { + funcRef.current = func; + }, [func]); + + useEffect(() => { + waitRef.current = wait; + }, [wait]); + + useEffect(() => { + return () => { + if (timeoutRef.current !== undefined) clearTimeout(timeoutRef.current); + }; + }, []); + + const debouncedRef = useRef(null as unknown as T); + if (!debouncedRef.current) { + debouncedRef.current = ((...args: Parameters) => { + if (timeoutRef.current !== undefined) clearTimeout(timeoutRef.current); + timeoutRef.current = window.setTimeout(() => { + timeoutRef.current = undefined; + funcRef.current(...args); + }, waitRef.current); + }) as T; + } + + return debouncedRef.current; } diff --git a/src/lib/article.ts b/src/lib/article.ts index a3e7b0d..cf29f21 100644 --- a/src/lib/article.ts +++ b/src/lib/article.ts @@ -6,7 +6,7 @@ import { CACHE_TAGS } from '@/lib/cache-tags'; type ArticlePagingParams = { pageNum?: number; pageSize?: number; - key?: string; + title?: string; }; export async function getArticleCacheAPI(id: number) { @@ -25,19 +25,19 @@ export async function getArticlePagingCacheAPI(params: ArticlePagingParams = {}) 'use cache'; const pageNum = params.pageNum ?? 1; const pageSize = params.pageSize ?? 8; - const key = params.key ?? ''; + const title = params.title ?? ''; cacheLife('blog'); cacheTag( CACHE_TAGS.articles, `${CACHE_TAGS.articlesList}-${pageNum}-${pageSize}`, - ...(key ? [`${CACHE_TAGS.articlesList}-search-${key}`] : []), + ...(title ? [`${CACHE_TAGS.articlesList}-search-${title}`] : []), ); return getArticlePagingAPI({ pageNum, pageSize, - ...(key ? { key } : {}), + ...(title ? { title } : {}), }); }