Dependent 与 Parallel Queries

TanStack Query 中两种常见查询模式:依赖查询(一个查询的结果驱动另一个查询)和并行查询(同时发起多个独立查询)。

#type / howto #status / evergreen #tech / dev / frontend #resource / react

[!info] related notes

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>
);
创建于 2026/7/3 更新于 2026/7/15