React Query实现初始挂载与搜索切换数据的问题求助
解决React Query实现Pokédex搜索时数据自动回退的问题
问题原因分析
你的代码出现点击搜索后数据短暂变化又恢复的情况,核心原因有两个:
- Query Key缓存冲突:初始获取数据和搜索用了同一个
queryKey: ["pokemons"],React Query会将这两个请求的缓存存储在同一key下。初始挂载的请求是自动启用的,当它完成后会覆盖搜索请求的缓存结果,导致数据回退。 - 过滤逻辑错误:你用
map函数做过滤,会生成包含undefined的数组(不匹配的项会返回undefined),这不仅会导致渲染问题,也不符合过滤的预期。
另外,搜索功能没必要重新发起全量API请求,基于已获取的本地数据做过滤更高效,也能避免缓存冲突。
解决方案:本地过滤实现搜索
1. 重构全量数据获取Hook
确保使用唯一的Query Key,避免缓存冲突:
export const fetchPokemon = async (URL: string) => { const result = await axios.get<pokemon>(URL); return result; }; export const fetchAllPokemons = async () => { const URL = "https://pokeapi.co/api/v2/pokemon?limit=100"; const { data } = await axios.get<pokemons>(URL); const result = await Promise.all( data.results.map(async (pokemon) => fetchPokemon(pokemon.url)) ); return result; }; export const useAllPokemons = () => { return useQuery({ queryKey: ["all-pokemons"], // 唯一标识全量数据的Query Key queryFn: fetchAllPokemons, }); };
2. 组件内实现搜索逻辑
用useState存储搜索关键词,结合useMemo自动过滤数据(或点击按钮触发过滤):
import { useState, useMemo } from 'react'; import { useAllPokemons } from './your-hook-path'; const Pokedex = () => { const { data: allPokemons, isLoading } = useAllPokemons(); const [searchTerm, setSearchTerm] = useState(""); // 自动根据搜索关键词过滤数据,无需点击按钮 const filteredPokemons = useMemo(() => { if (!allPokemons || !searchTerm.trim()) return allPokemons; // 过滤出名称以搜索词开头的宝可梦,忽略大小写 return allPokemons.filter(pokemon => pokemon.data.name.toLowerCase().startsWith(searchTerm.toLowerCase().trim()) ); }, [allPokemons, searchTerm]); // 如果需要点击按钮触发过滤,替换上面的useMemo为以下代码: // const [filteredPokemons, setFilteredPokemons] = useState(allPokemons); // const handleSearch = () => { // if (!allPokemons) return; // const filtered = allPokemons.filter(pokemon => // pokemon.data.name.toLowerCase().startsWith(searchTerm.toLowerCase().trim()) // ); // setFilteredPokemons(filtered); // }; return ( <div className="pokedex-container"> <input type="text" value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} placeholder="输入宝可梦名称搜索..." /> {/* 点击触发搜索的按钮(如果不用自动过滤则保留) */} {/* <button onClick={handleSearch}>搜索</button> */} {isLoading ? ( <div>加载宝可梦数据中...</div> ) : ( <div className="pokemon-list"> {filteredPokemons?.map(pokemon => ( <div key={pokemon.data.id} className="pokemon-card"> <h3>{pokemon.data.name}</h3> <img src={pokemon.data.sprites.front_default} alt={pokemon.data.name} /> </div> ))} </div> )} </div> ); }; export default Pokedex;
若需后端搜索的处理方式
如果你的搜索需求需要从API端获取结果(比如搜索海量数据),需为搜索请求设置包含关键词的唯一Query Key,避免和全量数据缓存冲突:
const [searchTerm, setSearchTerm] = useState(""); const { data: searchResults, isFetching: isSearching } = useQuery({ queryKey: ["pokemon-search", searchTerm], // 将搜索关键词加入Query Key queryFn: async () => { // 示例:调用后端搜索接口(PokeAPI支持单个宝可梦名称查询,批量需自行处理) const response = await axios.get(`https://pokeapi.co/api/v2/pokemon/${searchTerm.toLowerCase()}`); return [response.data]; // 统一格式为数组,方便渲染 }, enabled: !!searchTerm.trim(), // 仅当有搜索词时发起请求 onError: () => { // 处理搜索不到的情况 console.log("未找到匹配的宝可梦"); }, });
关键注意点
- Query Key唯一性:不同的请求逻辑必须用不同的Query Key,避免React Query缓存互相覆盖。
- 过滤逻辑正确性:用
filter替代map做数据筛选,避免生成包含undefined的无效数组。 - 优先本地过滤:已获取的数据直接在前端过滤,减少API请求次数和延迟。
内容的提问来源于stack exchange,提问作者onlylooker
相关产品推荐
相关产品推荐

