如何在Next.js项目中动态切换React Query查询?
问题描述
我有一个基于Next.js和React Query的项目,需求如下:
- 首页展示API返回的所有国家
- 提供按名称搜索的输入框和按地区筛选的下拉选择器
- 根据筛选条件动态切换查询调用,不想通过useState存储数据展示,要充分利用React Query的缓存与自动更新能力
以下是现有代码结构:
现有代码
countries.service.ts
import { Regions } from '@/queries/types'; import axios from 'axios'; export class CountriesService { static client = axios.create({ baseURL: `${process.env.NEXT_PUBLIC_API_URL}`, }); static async getAllCountries() { const response = await CountriesService.client.get('/all'); return response.data; } static async getCountryByName(name: string) { const response = await CountriesService.client.get(`/name/${name}`); return response.data; } static async getCountryByRegion(region: Regions) { const response = await CountriesService.client.get(`/region/${region}`); return response.data; } }
countries.query.ts
import { useQuery } from '@tanstack/react-query'; import { CountriesService } from '@/services/countries.service'; import { Regions } from './types'; export class CountriesQuery { static getAllCountries() { return useQuery({ queryKey: ['get-all-countries'], queryFn: async () => { const data = await CountriesService.getAllCountries(); return data; }, }); } static getCountryByName({ name }: { name: string }) { return useQuery({ queryKey: ['get-country-by-name'], queryFn: async () => { const data = await CountriesService.getCountryByName(name); return data; }, }); } static getCountryByRegion({ region }: { region: Regions }) { return useQuery({ queryKey: ['get-country-by-region'], queryFn: async () => { const data = await CountriesService.getCountryByName(region); return data; }, }); } }
page.tsx
'use client'; import { CountryCard } from "@/components/country-card"; import { CountriesQuery } from "@/queries/countries.query"; import { Country } from "@/queries/types"; import { MagnifyingGlass } from "@phosphor-icons/react"; import { useState } from "react"; export default function Home() { const [searchName, setSearchName] = useState(''); const { data, isLoading} = CountriesQuery.getAllCountries(); const handleSearchByName = async (e: React.ChangeEvent<HTMLInputElement>) => { setSearchName(e.target.value); }; if (isLoading) { //make skeleton return; }; return ( <main> <div> <input onChange={handleSearchByName} placeholder="Search for a country..." /> <button><MagnifyingGlass weight="bold" /></button> </div> <div> {data.map((country: Country) => ( <div key={country.name.common} > <CountryCard flagSrc={country.flags.png} name={country.name.common} population={country.population} region={country.region} capital={country.capital} /> </div> ))} </div> </main> ) }
解决方案
第一步:修复并优化countries.query.ts
首先修正原代码中的错误(getCountryByRegion错误调用了getCountryByName),同时给查询键加入参数,让React Query能根据不同参数缓存独立结果:
import { useQuery } from '@tanstack/react-query'; import { CountriesService } from '@/services/countries.service'; import { Regions } from './types'; export class CountriesQuery { static getAllCountries() { return useQuery({ queryKey: ['countries'], queryFn: CountriesService.getAllCountries, }); } static getCountryByName({ name }: { name: string }) { return useQuery({ queryKey: ['countries', 'name', name], queryFn: () => CountriesService.getCountryByName(name), enabled: !!name, // 仅当搜索词非空时执行查询 }); } static getCountryByRegion({ region }: { region: Regions }) { return useQuery({ queryKey: ['countries', 'region', region], queryFn: () => CountriesService.getCountryByRegion(region), enabled: !!region, // 仅当选择地区时执行查询 }); } }
第二步:在page.tsx中整合动态筛选逻辑
维护筛选状态,根据当前条件自动切换对应的React Query查询,无需手动存储数据:
'use client'; import { CountryCard } from "@/components/country-card"; import { CountriesQuery } from "@/queries/countries.query"; import { Country, Regions } from "@/queries/types"; import { MagnifyingGlass } from "@phosphor-icons/react"; import { useState } from "react"; export default function Home() { const [searchName, setSearchName] = useState(''); const [selectedRegion, setSelectedRegion] = useState<Regions | ''>(''); // 根据筛选条件动态选择查询 const { data: countries, isLoading, error } = (() => { if (searchName) { return CountriesQuery.getCountryByName({ name: searchName }); } if (selectedRegion) { return CountriesQuery.getCountryByRegion({ region: selectedRegion }); } return CountriesQuery.getAllCountries(); })(); const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => { setSearchName(e.target.value); setSelectedRegion(''); // 搜索时清空地区筛选,避免条件冲突 }; const handleRegionChange = (e: React.ChangeEvent<HTMLSelectElement>) => { setSelectedRegion(e.target.value as Regions); setSearchName(''); // 选择地区时清空搜索词,避免条件冲突 }; if (isLoading) { return <div>加载中...</div>; // 替换为你的骨架屏组件 } if (error) { return <div>加载失败:{(error as Error).message}</div>; } if (!countries?.length) { return <div>没有找到匹配的国家</div>; } return ( <main className="container mx-auto p-4"> <div className="flex flex-wrap gap-4 mb-8"> {/* 名称搜索框 */} <div className="relative flex items-center"> <input onChange={handleSearchChange} value={searchName} placeholder="Search for a country..." className="pl-10 pr-4 py-2 border rounded-md" /> <MagnifyingGlass className="absolute left-3" weight="bold" /> </div> {/* 地区筛选下拉框 */} <select onChange={handleRegionChange} value={selectedRegion} className="px-4 py-2 border rounded-md" > <option value="">所有地区</option> <option value="Africa">非洲</option> <option value="Americas">美洲</option> <option value="Asia">亚洲</option> <option value="Europe">欧洲</option> <option value="Oceania">大洋洲</option> </select> </div> {/* 国家卡片列表 */} <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> {countries.map((country: Country) => ( <div key={country.name.common} > <CountryCard flagSrc={country.flags.png} name={country.name.common} population={country.population} region={country.region} capital={country.capital} /> </div> ))} </div> </main> ) }
可选优化
- 搜索防抖:避免输入时频繁触发API请求,可使用React Query的
useDebounce:import { useDebounce } from '@tanstack/react-query'; // 组件内添加 const debouncedSearchName = useDebounce(searchName, 300); // 后续用debouncedSearchName代替searchName判断查询 - 缓存预取:对常用地区或热门搜索词提前预取数据,提升响应速度。
内容的提问来源于stack exchange,提问作者Laisa
相关产品推荐
相关产品推荐

