不提升状态,在另一个组件中使用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
相关产品推荐
相关产品推荐

