useSearchParams引发useEffect无限循环,求复刻anilist.co搜索功能方案
问题分析与解决方案
为什么会出现无限循环?
useSearchParams 返回的 URLSearchParams 对象每次组件渲染都会生成新的引用,哪怕地址栏的查询参数完全没变化。如果把整个 qp 对象放进 useEffect 的依赖数组,组件每次渲染都会触发 useEffect,发起API请求后组件重新渲染,又生成新的 qp 对象,最终导致无限循环。
快速修复方案
不要依赖整个 qp 对象,而是提取具体的查询参数值作为依赖:
import { useSearchParams } from 'react-router-dom'; import { useEffect, useState } from 'react'; const SearchPage = () => { const [qp] = useSearchParams(); // 提取具体的搜索参数值,而非整个qp对象 const searchQuery = qp.get('q'); const [results, setResults] = useState([]); useEffect(() => { // 空查询时跳过请求 if (!searchQuery?.trim()) { setResults([]); return; } // 发起Anilist API请求 const fetchAnime = async () => { try { const res = await fetch('https://api.anilist.co/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` query ($search: String) { Page(page: 1, perPage: 20) { media(search: $search, type: ANIME) { id title { romaji english } coverImage { large } } } } `, variables: { search: searchQuery } }) }); const data = await res.json(); setResults(data.data.Page.media); } catch (err) { console.error('搜索失败:', err); } }; fetchAnime(); }, [searchQuery]); // 仅当searchQuery实际变化时触发 // 渲染搜索框和结果... };
更优实现方案
针对Anilist搜索场景,推荐封装成可复用的自定义Hook,同时加入防抖、状态管理等优化:
1. 封装自定义Hook useAnimeSearch
import { useSearchParams, useNavigate } from 'react-router-dom'; import { useEffect, useState, useCallback } from 'react'; // 简易防抖实现(也可使用lodash.debounce) const debounce = (fn, delay) => { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn(...args), delay); }; }; const useAnimeSearch = () => { const [qp] = useSearchParams(); const navigate = useNavigate(); const [results, setResults] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const currentQuery = qp.get('q'); // 防抖后的请求函数,避免输入过程中频繁发起请求 const fetchSearchResults = useCallback( debounce(async (query) => { if (!query?.trim()) { setResults([]); return; } setLoading(true); setError(null); try { const res = await fetch('https://api.anilist.co/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ` query ($search: String) { Page(page: 1, perPage: 20) { media(search: $search, type: ANIME) { id title { romaji english } coverImage { large } startDate { year } } } } `, variables: { search: query } }) }); if (!res.ok) throw new Error('请求失败'); const data = await res.json(); setResults(data.data.Page.media); } catch (err) { setError('搜索失败,请稍后重试'); } finally { setLoading(false); } }, 300), // 300ms防抖延迟 [] ); // 监听查询参数变化 useEffect(() => { fetchSearchResults(currentQuery || ''); // 组件卸载时清理防抖定时器 return () => fetchSearchResults.cancel(); }, [currentQuery, fetchSearchResults]); // 更新地址栏查询参数的方法(输入框联动地址栏) const updateSearchQuery = (query) => { const newParams = new URLSearchParams(qp); if (query.trim()) { newParams.set('q', query); } else { newParams.delete('q'); } // 使用replace避免历史记录堆积 navigate(`?${newParams.toString()}`, { replace: true }); }; return { results, loading, error, currentQuery, updateSearchQuery }; };
2. 使用自定义Hook构建搜索页面
const SearchPage = () => { const { results, loading, error, currentQuery, updateSearchQuery } = useAnimeSearch(); return ( <div className="search-container"> <div className="search-bar"> <input type="text" value={currentQuery || ''} onChange={(e) => updateSearchQuery(e.target.value)} placeholder="搜索动画..." className="search-input" /> </div> {loading && <div className="loading">加载中...</div>} {error && <div className="error-message">{error}</div>} <div className="results-grid"> {results.map((anime) => ( <div key={anime.id} className="anime-card"> <img src={anime.coverImage.large} alt={anime.title.english || anime.title.romaji} className="anime-cover" /> <div className="anime-info"> <h3>{anime.title.english || anime.title.romaji}</h3> {anime.startDate.year && <p>{anime.startDate.year}</p>} </div> </div> ))} </div> </div> ); };
优化点说明
- 防抖处理:用户输入过程中仅在停止输入300ms后发起请求,减少无效API调用
- 状态管理:统一处理加载、错误、结果状态,提升用户体验
- 路由同步:输入框内容与地址栏参数双向同步,支持页面刷新后保留搜索状态
- 复用性:自定义Hook可在多个页面复用,减少重复代码
内容的提问来源于stack exchange,提问作者ayushwashere
相关产品推荐
相关产品推荐

