Dependent 与 Parallel Queries
TanStack Query 中两种常见查询模式:依赖查询(一个查询的结果驱动另一个查询)和并行查询(同时发起多个独立查询)。
#type / howto
#status / evergreen
#tech / dev / frontend
#resource / react
[!info] related notes
- 前置: TanStack Query 服务端状态
- 所属 MOC: TanStack Query 知识地图
- 关联: select 与数据转换
- 关联: 预取策略与 Suspense
Dependent 与 Parallel Queries
Dependent Queries — 依赖查询
核心问题
有时第二个请求的参数来自第一个请求的结果:
先请求用户信息 → 拿到用户的 projectId → 再请求该项目的详情
实现方式
function UserProject({ userId }) {
// 第一步:获取用户信息
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// 第二步:依赖 user.projectId
const { data: project } = useQuery({
queryKey: ['project', user?.projectId],
queryFn: () => fetchProject(user!.projectId),
enabled: !!user?.projectId, // 关键:user 没拿到之前不请求
});
if (!user) return <Loading />;
if (!project) return <Loading project />;
return (
<div>
<h1>{user.name}</h1>
<p>项目: {project.name}</p>
</div>
);
}
enabled 的作用
user === undefined
→ enabled: false → 不发请求,query 状态为 pending
user 加载完成
→ enabled: true → 发请求
→ queryKey 变为 ['project', 'proj-123']
→ 请求发出
多级依赖
// 三级依赖:用户 → 组织 → 订阅
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
const { data: org } = useQuery({
queryKey: ['org', user?.orgId],
queryFn: () => fetchOrg(user!.orgId),
enabled: !!user?.orgId,
});
const { data: subscription } = useQuery({
queryKey: ['subscription', org?.subscriptionId],
queryFn: () => fetchSubscription(org!.subscriptionId),
enabled: !!org?.subscriptionId,
});
合并依赖查询
如果依赖链太长,可以后端提供一个合并接口:
// ❌ 三级串行请求
// 用户 → 组织 → 订阅(3 次网络往返)
// ✅ 后端提供聚合接口
const { data } = useQuery({
queryKey: ['user-profile', userId],
queryFn: () => fetchUserProfile(userId), // 一次返回 user + org + subscription
});
Parallel Queries — 并行查询
核心问题
多个独立查询同时发起,不需要串行等待:
// ❌ 串行:总时间 = A + B + C
const users = await fetchUsers();
const posts = await fetchPosts();
const comments = await fetchComments();
// ✅ 并行:总时间 = max(A, B, C)
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
TanStack Query 的天然并行
多个 useQuery 调用天然并行,不需要额外配置:
function Dashboard() {
// 这三个请求同时发出
const usersQuery = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
});
const postsQuery = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
const statsQuery = useQuery({
queryKey: ['stats'],
queryFn: fetchStats,
});
// 任何一个失败都不影响其他
if (usersQuery.isLoading) return <LoadingUsers />;
if (postsQuery.isLoading) return <LoadingPosts />;
return (
<div>
<UserList data={usersQuery.data} />
<PostList data={postsQuery.data} />
{statsQuery.data && <Stats data={statsQuery.data} />}
</div>
);
}
动态并行查询
当查询数量动态变化时:
function MultiResource({ ids }) {
// 每个 id 一个并行查询
const queries = ids.map(id => useQuery({
queryKey: ['resource', id],
queryFn: () => fetchResource(id),
enabled: !!id,
}));
const allLoading = queries.some(q => q.isLoading);
const allData = queries.map(q => q.data);
const errors = queries.filter(q => q.isError).map(q => q.error);
if (allLoading) return <Loading />;
if (errors.length) return <ErrorList errors={errors} />;
return <ResourceList data={allData} />;
}
注意:Hooks 数量不能动态变化。如果 ids 数组长度会变,用 useQueries:
import { useQueries } from '@tanstack/react-query';
function MultiResource({ ids }) {
const results = useQueries({
queries: ids.map(id => ({
queryKey: ['resource', id],
queryFn: () => fetchResource(id),
})),
});
const allLoading = results.some(r => r.isLoading);
const allData = results.map(r => r.data);
// ...
}
useQueries 的优势
// useQueries 返回数组,数量可以动态变化
const results = useQueries({
queries: items.map(item => ({
queryKey: ['item', item.id],
queryFn: () => fetchItem(item.id),
staleTime: 60_000,
})),
combine: (results) => {
// v5:可以在 combine 中做聚合
return {
data: results.map(r => r.data),
isLoading: results.some(r => r.isLoading),
isError: results.some(r => r.isError),
};
},
});
组合模式:依赖 + 并行
function ProjectDashboard({ userId }) {
// 依赖查询:先拿用户
const { data: user } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetchUser(userId),
});
// 并行查询:用户拿到后同时请求多个数据
const projectQuery = useQuery({
queryKey: ['project', user?.projectId],
queryFn: () => fetchProject(user!.projectId),
enabled: !!user?.projectId,
});
const membersQuery = useQuery({
queryKey: ['project', user?.projectId, 'members'],
queryFn: () => fetchProjectMembers(user!.projectId),
enabled: !!user?.projectId,
});
const tasksQuery = useQuery({
queryKey: ['project', user?.projectId, 'tasks'],
queryFn: () => fetchProjectTasks(user!.projectId),
enabled: !!user?.projectId,
});
// project、members、tasks 三个请求并行发出
// ...
}
常见错误
依赖查询忘记 enabled
// ❌ user 还没拿到,就尝试用 user.orgId
const { data: org } = useQuery({
queryKey: ['org', user.orgId], // user 是 undefined → 报错
queryFn: () => fetchOrg(user.orgId),
});
// ✅ 用 optional chaining + enabled
const { data: org } = useQuery({
queryKey: ['org', user?.orgId],
queryFn: () => fetchOrg(user!.orgId),
enabled: !!user?.orgId,
});
并行查询中一个失败影响全部
// ❌ 一个查询失败,整个页面白屏
const usersQuery = useQuery({ ... });
const postsQuery = useQuery({ ... });
// 如果 usersQuery 失败,下面的代码不会执行
// ✅ 独立处理每个查询的状态
return (
<div>
{usersQuery.isError ? <Error /> : <UserList data={usersQuery.data} />}
{postsQuery.isError ? <Error /> : <PostList data={postsQuery.data} />}
</div>
);