You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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>
  )
}

可选优化

  1. 搜索防抖:避免输入时频繁触发API请求,可使用React Query的useDebounce:
    import { useDebounce } from '@tanstack/react-query';
    
    // 组件内添加
    const debouncedSearchName = useDebounce(searchName, 300);
    // 后续用debouncedSearchName代替searchName判断查询
    
  2. 缓存预取:对常用地区或热门搜索词提前预取数据,提升响应速度。

内容的提问来源于stack exchange,提问作者Laisa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 17:10:20