优秀的编程知识分享平台

网站首页 > 技术文章 正文

大厂都在用 @tanstack/react-query

nanyue 2025-09-18 23:57:55 技术文章 1 ℃

@tanstack/react-query(原 react-query)之所以被大厂广泛采用,核心在于它解决了前端开发中服务端状态管理的痛点,让数据请求、缓存、同步等操作变得极其简洁高效。它的 "好用" 主要体现在以下几个方面:

准备

pnpm add @tanstack/react-query

传统方式与react-query 方式

// 传统方式
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  let isMounted = true;
  fetch('/api/users')
    .then(res => res.json())
    .then(data => { if (isMounted) setData(data); })
    .catch(err => { if (isMounted) setError(err); })
    .finally(() => { if (isMounted) setLoading(false); });
  
  return () => { isMounted = false; }; // 解决竞态问题
}, []);

// react-query 方式
const { data, isLoading, error } = useQuery({
  queryKey: ['users'], // 唯一标识,用于缓存
  queryFn: () => fetch('/api/users').then(res => res.json())
});

集成到项目

import React from"react";
import {
  QueryClient,
  QueryClientProvider
} from"@tanstack/react-query";
import Layout from"@/Layout";

exportdefaultfunction App() {
// 创建 QueryClient 实例
const queryClient = new QueryClient();

return (
    <QueryClientProvider client={queryClient}>
      <Layout />
    </QueryClientProvider>
  );
}

useQuery 请求数据

useQuery 用于数据获取,支持自动缓存、重取和错误处理!

import React from"react";
import { useQuery } from"@tanstack/react-query";
import axios from"axios";

function fetchUser(userId: number) {
return axios.get(`/api/users`).then(res => res.data);
}

function User({ userId }: { userId: number }) {
    const { data, isLoading, isError, error, refetch } = useQuery({
        queryKey: ["user", userId], // 缓存 key
        queryFn: () => fetchUser(userId),
        // 数据在缓存中保持 1 分钟为新鲜,不会重复请求
        staleTime: 1000 * 60,
        // 数据缓存 5 分钟
        cacheTime: 1000 * 60 * 5,
        // 请求失败自动重试 2 次
        retry: 2,
        onError: (err) => {
          console.error("获取用户数据失败:", err);
        }
      });

    if (isLoading) return<div>加载中...</div>;
    if (isError) return<div>出错了: {(error as Error).message}</div>;

    return (
        <div>
          <h2>{data.name}</h2>
          <p>Email: {data.email}</p>
          <button onClick={() => refetch()}>手动触发重取</button>
        </div>
      );
}

突变与更新(useMutation)

useMutation 用于数据更新,如 POST 请求,支持乐观更新和回滚。

import React, { useState } from"react";
import {
  useQuery,
  useMutation,
  useQueryClient
} from"@tanstack/react-query";
import axios from"axios";


// 模拟获取待办列表
const fetchTodos = () => axios.get("/api/todos").then(res => res.data);

// 模拟添加待办
const addTodo = (text: string) => axios.post("/api/todos", { text }).then(res => res.data);

exportfunction Todos() {
const queryClient = useQueryClient();
const [newText, setNewText] = useState("");

// 获取待办列表
const { data: todos = [] } = useQuery({ queryKey: ["todos"], queryFn: fetchTodos });

// useMutation 用于新增待办
const mutation = useMutation({
    mutationFn: addTodo,

    // 乐观更新
    onMutate: async (text: string) => {
      await queryClient.cancelQueries({ queryKey: ["todos"] });

      const previousTodos = queryClient.getQueryData<any[]>(["todos"]);

      // 立即更新缓存,显示新增项
      queryClient.setQueryData(["todos"], [...(previousTodos || []), { id: Date.now(), text }]);

      return { previousTodos }; // 返回上下文,用于失败回滚
    },

    // 出错时回滚
    onError: (_err, _variables, context: any) => {
      if (context?.previousTodos) {
        queryClient.setQueryData(["todos"], context.previousTodos);
      }
    },

    // 成功后刷新缓存
    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ["todos"] });
    }
  });

return (
    <div>
      <h2>待办列表</h2>
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>

      <input value={newText} onChange={e => setNewText(e.target.value)} placeholder="新增" />
      <button onClick={() => mutation.mutate(newText)}>添加</button>
    </div>
  );
}

并行/依赖查询

const userQuery = useQuery({
    queryKey: ["user", userId],
    queryFn: () => fetchUser(userId),
});

const postsQuery = useQuery({
    queryKey: ["posts", userId],
    queryFn: () => fetchPosts(userId),
});

如果需要依赖查询,则需要使用 enabled 属性!

const userQuery = useQuery({
    queryKey: ["userByName", username],
    queryFn: () => axios.get(`/api/users?username=${username}`).then(res => res.data),
});

const postsQuery = useQuery({
    queryKey: ["posts", userQuery.data?.id],
    queryFn: () => axios.get(`/api/users/${userQuery.data.id}/posts`).then(res => res.data),
    // 只有当 userQuery 获取到 id 时才触发
    enabled: !!userQuery.data?.id,
});

与框架无关,生态完善

tanstack 系列已支持 React、Vue、Svelte 等框架,API 设计一致,学习成本低。同时提供:

  • 开发者工具(DevTools):可视化缓存状态、请求历史,方便调试
  • 丰富的配置项:几乎所有行为都可自定义(缓存策略、重试机制、失效规则等)
  • 类型安全:完美支持 TypeScript,减少类型错误

为什么大厂爱用?

对于复杂应用(如中台、电商、社交产品),服务端数据交互频繁,传统方式容易导致代码冗余、状态混乱、性能问题。react-query 用 "声明式" 的方式统一管理这些逻辑,让团队能更专注于业务,而非重复处理请求细节。

简单说:它让数据请求从 "手动挡" 变成了 "自动挡",这也是它被称为 "前端请求管理的终极方案" 的原因。

最近发表
标签列表