字节也在用的 @tanstack/react-query 到底有多好用!🔥🔥🔥

前言

最近参与新的项目了,发现项目居然使用了 @tanstack/react-query,以前之后简单介绍过,今天再来详细上手使用下!

往期精彩推荐

正文

@tanstack/react-query 是 TanStack 生态的一部分,专为 React、Vue 等框架设计,用于管理服务器状态和异步数据。

它无需全局状态库,就能处理数据获取、缓存、突变和同步,支持 TS/JS,并提供开发工具。

以下是 React Query 的详细使用过程

准备

bash 复制代码
pnpm add @tanstack/react-query

然后在根入口处使用 Provider!

js 复制代码
import React from "react";
import {
  QueryClient,
  QueryClientProvider
} from "@tanstack/react-query";
import Layout from "@/Layout";

export default function App() {
  // 创建 QueryClient 实例
  const queryClient = new QueryClient();
  
  return (
    <QueryClientProvider client={queryClient}>
      <Layout />
    </QueryClientProvider>
  );
}

useQuery 请求数据

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

javascript 复制代码
import React from "react";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";

function fetchUser(userId: number) {
  return axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`).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>
  );
}

staleTime 的默认值为 0 ,也就是说立马失效,不会缓存!

2. 突变与更新(useMutation)

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

javascript 复制代码
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);

export function 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>
  );
}

并行/依赖查询

并行查询时只需要写多个 useQuery 即可,

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

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

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

js 复制代码
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,
});

开发工具集成

开发时,可以搭配它的开发者工具,

js 复制代码
import React from "react";
import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import Layout from "@/Layout";

// 创建 QueryClient
const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Layout />
      <ReactQueryDevtools initialIsOpen={false} position="bottom-right" />
    </QueryClientProvider>
  );
}

点击打开控制面板,是这样的:

最后

@tanstack/react-query 解决了数据管理的痛点,通过 useQueryuseMutation 等钩子,让代码更简洁、更可靠。感兴趣的话感觉用它来优化你的项目吧!

今天的分享就这些了,感谢大家的阅读!如果文章中存在错误的地方欢迎指正!

往期精彩推荐

相关推荐
ObjectX前端实验室19 分钟前
【react18原理探究实践】异步可中断 & 时间分片
前端·react.js
SoaringHeart22 分钟前
Flutter进阶:自定义一个 json 转 model 工具
前端·flutter·dart
努力打怪升级24 分钟前
Rocky Linux 8 远程管理配置指南(宿主机 VNC + KVM 虚拟机 VNC)
前端·chrome
brzhang1 小时前
AI Agent 干不好活,不是它笨,告诉你一个残忍的现实,是你给他的工具太难用了
前端·后端·架构
brzhang1 小时前
一文说明白为什么现在 AI Agent 都把重点放在上下文工程(context engineering)上?
前端·后端·架构
reembarkation1 小时前
自定义分页控件,只显示当前页码的前后N页
开发语言·前端·javascript
gerrgwg2 小时前
React Hooks入门
前端·javascript·react.js
ObjectX前端实验室2 小时前
【react18原理探究实践】调度机制之注册任务
前端·react.js
汉字萌萌哒2 小时前
【 HTML基础知识】
前端·javascript·windows
ObjectX前端实验室2 小时前
【React 原理探究实践】root.render 干了啥?——深入 render 函数
前端·react.js