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

不提升状态,在另一个组件中使用React Query数据的方案

问题描述

我正在使用Next.js + TypeScript + React Query开发应用,当前应用有一个输入框用于获取用户输入的URL,该URL会被用于获取GitHub Issue列表。以下是简化后的文件内容:

url-form.tsx

"use client";

import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { useIssues } from "@/hooks/use-issues";
import { urlFormSchema } from "@/lib/schema";
//form imports

type FormValues = z.infer<typeof urlFormSchema>;

export default function GithubUrlForm() {
  const [url, setUrl] = useState<string>("");

  const form = useForm<FormValues>({
    resolver: zodResolver(urlFormSchema),
    defaultValues: { url: "" },
  });

  const { data: issues, error, isError, isLoading, isSuccess } = useIssues(url);

  const onSubmit = (data: FormValues) => {
    queryClient.removeQueries({ queryKey: ["github-issues"] });
    setUrl(data.url);
  };

  return (
    <Form {...form}>
      <form
        onSubmit={form.handleSubmit(onSubmit)}
      >
        <div className="flex-grow">
          <FormField
            control={form.control}
            name="url"
            render={({ field }) => (
              <FormItem className="w-full">
                <FormControl>
                  <Input   
                    placeholder="Enter your Github repository URL"
                    {...field}
                  />
                </FormControl>
              </FormItem>
            )}
          />
        </div>

        <Button type="submit" disabled={isLoading}>
          {isLoading ? "Fetching..." : "Fetch Issues!"}
        </Button>
      </form>
    </Form>
  );
}

page.tsx (首页)

import GithubUrlForm from "@/components/url-form";
import { QueryClient, HydrationBoundary, dehydrate } from "@tanstack/react-query";

export default function Home() {
  const queryClient = new QueryClient();
  return (
    <main>
      <HydrationBoundary state={dehydrate(queryClient)}>
        <GithubUrlForm />
      </HydrationBoundary>
    </main>
  );
}

use-issues.tsx

import { useQuery } from "@tanstack/react-query";
import { Octokit } from "@octokit/rest";

const octokit = new Octokit();

const extractRepoInfo = (url: string) => {
  // some regex
};

const fetchIssues = async (url: string) => {
  const { owner, repo } = extractRepoInfo(url);
  const { data } = await octokit.issues.listForRepo({ owner, repo, state: "open" });
  return data;
};

export const useIssues = (url: string) => {
  return useQuery({
    queryKey: ["github-issues", url],
    queryFn: () => fetchIssues(url),
  });
};

现在我希望在另一个组件中访问该Issue列表并渲染到UI上,但希望避免将URL状态提升至Home组件(需保持Home为服务端组件),请问最优的实现方式是什么?


最优实现方案

核心思路:利用React Query全局缓存共享数据

React Query的查询缓存是全局可访问的,只要其他组件能匹配到对应的queryKey,就能直接获取缓存的Issue数据,无需状态提升到服务端组件。

具体步骤

1. 优化GithubUrlForm,存储当前有效URL到缓存

在GithubUrlForm中,提交表单时把用户输入的URL存入React Query缓存,供其他组件读取:

// 在url-form.tsx中引入useQueryClient
import { useQueryClient } from "@tanstack/react-query";

export default function GithubUrlForm() {
  const queryClient = useQueryClient(); // 添加这行
  const [url, setUrl] = useState<string>("");
  
  // ...其他代码保持不变

  const onSubmit = (data: FormValues) => {
    // 存储当前URL到缓存
    queryClient.setQueryData(["current-repo-url"], data.url);
    setUrl(data.url);
  };

  // ...其余代码不变
}

2. 创建Issue列表组件,从缓存读取数据

新建客户端组件IssueList.tsx,通过缓存获取当前URL,再用该URL匹配Issue数据的查询缓存:

"use client";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { fetchIssues } from "@/hooks/use-issues";

export default function IssueList() {
  const queryClient = useQueryClient();
  // 从缓存获取当前有效的仓库URL
  const currentUrl = queryClient.getQueryData<string>(["current-repo-url"]) || "";

  // 复用useIssues的逻辑,仅当URL存在时发起请求
  const { data: issues, isLoading, isError } = useQuery({
    queryKey: ["github-issues", currentUrl],
    queryFn: () => fetchIssues(currentUrl),
    enabled: !!currentUrl, // URL为空时不触发请求
  });

  // 状态渲染逻辑
  if (isLoading) return <div>加载中...</div>;
  if (isError) return <div>加载失败,请检查URL是否正确</div>;
  if (!issues) return <div>请先输入GitHub仓库URL</div>;

  // 渲染Issue列表
  return (
    <div className="mt-8">
      <h3 className="text-xl font-bold mb-4">Issue列表</h3>
      <ul className="space-y-2">
        {issues.map((issue) => (
          <li key={issue.id} className="p-3 border rounded">
            <a href={issue.html_url} target="_blank" rel="noopener noreferrer">
              {issue.title}
            </a>
          </li>
        ))}
      </ul>
    </div>
  );
}

3. 在Home组件中引入IssueList

Home作为服务端组件,直接引入客户端组件IssueList即可,Next.js会自动处理客户端组件的 hydration:

import GithubUrlForm from "@/components/url-form";
import IssueList from "@/components/issue-list"; // 新增引入
import { QueryClient, HydrationBoundary, dehydrate } from "@tanstack/react-query";

export default function Home() {
  const queryClient = new QueryClient();
  return (
    <main className="max-w-4xl mx-auto p-6">
      <HydrationBoundary state={dehydrate(queryClient)}>
        <GithubUrlForm />
        <IssueList /> {/* 新增组件 */}
      </HydrationBoundary>
    </main>
  );
}

4. 优化原useIssues逻辑(可选)

为避免URL为空时发起无效请求,修改use-issues.tsx:

export const useIssues = (url: string) => {
  return useQuery({
    queryKey: ["github-issues", url],
    queryFn: () => fetchIssues(url),
    enabled: !!url, // URL为空时不执行查询
  });
};

方案优势

  • 无需将URL状态提升到服务端组件Home,保持Home的服务端渲染特性,避免不必要的前后端交互。
  • 利用React Query的缓存机制,自动处理数据同步、缓存失效和重新请求,减少重复代码。
  • 组件通过缓存解耦,无需依赖层级传递状态,扩展性更强。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 14:22:08