为什么很多大项目都在用 @tanstack/react-query?它解决了什么问题

摘要:@tanstack/react-query 解决了 React 项目中数据管理的核心问题。它让你的代码更简洁,用户体验更好,开发效率更高。虽然需要一些学习成本,但一旦用起来,你会发现它大大简化了数据获取和状态管理的工作。

如果你在做 React 项目,并且需要从后端获取数据,那你很可能遇到过这些情况:

  • 同样的数据在不同组件里重复请求

  • 用户操作后,界面数据没有及时更新

  • 加载状态和错误状态需要自己处理

  • 分页、无限滚动等功能实现起来很麻烦

@tanstack/react-query(原名 React Query)就是专门为解决这些问题而生的。它是一个专门管理服务器状态的数据获取库。


它能帮你做什么

简单来说,react-query 帮你处理所有跟后台数据相关的工作:

  • 自动缓存数据,避免重复请求

  • 数据过期后自动重新获取

  • 错误自动重试

  • 乐观更新(先更新界面,再发送请求)

  • 分页和无限滚动支持

你不再需要把这些逻辑写在 Redux 或者 Context 里,代码会简洁很多。


开始使用

安装:

pnpm add @tanstack/react-query

在应用最外层设置:

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

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

这样你的所有组件就都能使用 react-query 的功能了。


获取数据:useQuery

看一个获取用户信息的例子:

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

// 定义获取数据的函数
function fetchUser(userId) {
  return axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`)
    .then(res => res.data);
}

function User({ userId }) {
  const { data, isLoading, isError, error, refetch } = useQuery({
    queryKey: ['user', userId],  // 缓存的键名
    queryFn: () => fetchUser(userId),  // 获取数据的函数
    staleTime: 1000 * 60,  // 1分钟内数据是新鲜的,不会重新请求
    cacheTime: 1000 * 60 * 5,  // 数据缓存5分钟
    retry: 2,  // 失败自动重试2次
  });

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

  return (
    <div>
      <h2>{data.name}</h2>
      <p>邮箱: {data.email}</p>
      <button onClick={() => refetch()}>重新加载</button>
    </div>
  );
}

这里有几个重要的参数:

  • queryKey:数据的唯一标识,相同 key 的数据会被缓存和复用

  • staleTime:数据保鲜时间,这段时间内不会重新请求

  • cacheTime:数据缓存时间,即使组件卸载了数据还会保留


修改数据:useMutation

当你需要添加、修改或删除数据时,可以用 useMutation:

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) => 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 
  });

  // 添加新待办
  const mutation = useMutation({
    mutationFn: addTodo,
    
    // 乐观更新:先更新界面,再发送请求
    onMutate: async (text) => {
      // 取消正在进行的请求,避免冲突
      await queryClient.cancelQueries({ queryKey: ['todos'] });
      
      // 保存当前数据,用于出错时回滚
      const previousTodos = queryClient.getQueryData(['todos']);
      
      // 立即更新缓存
      queryClient.setQueryData(['todos'], [...(previousTodos || []), { 
        id: Date.now(), 
        text,
        // 临时标识,等真实请求成功后会替换
        optimistic: true 
      }]);
      
      return { previousTodos };
    },
    
    // 出错时回滚到之前的状态
    onError: (err, variables, context) => {
      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}
            {todo.optimistic && ' (保存中...)'}
          </li>
        ))}
      </ul>

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

乐观更新让用户体验更好,用户不用等待请求完成就能看到结果。


处理数据依赖关系

有时候你需要先获取一个数据,然后基于这个数据获取另一个数据:

// 先获取用户信息
const userQuery = useQuery({
  queryKey: ['userByName', username],
  queryFn: () => axios.get(`/api/users?username=${username}`).then(res => res.data),
});

// 等获取到用户ID后,再获取用户的文章
const postsQuery = useQuery({
  queryKey: ['posts', userQuery.data?.id],
  queryFn: () => axios.get(`/api/users/${userQuery.data.id}/posts`).then(res => res.data),
  // 只有拿到用户ID后才执行
  enabled: !!userQuery.data?.id,
});

enabled 参数让你能够控制什么时候执行请求。


开发工具

react-query 提供了开发工具,让你在开发时能看到缓存的数据、正在进行的请求等信息:

import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

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

安装开发工具包:

pnpm add @tanstack/react-query-devtools


实际项目中的建议

  1. 统一管理查询键名:把所有的 queryKey 放在一个文件里管理,避免拼写错误

  2. 封装自定义 Hooks:把 useQuery 和 useMutation 封装成自定义 Hook,方便复用

  3. 设置全局默认值:可以配置默认的 staleTime 和 cacheTime

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5分钟
      cacheTime: 1000 * 60 * 30, // 30分钟
    },
  },
});


总结

@tanstack/react-query 解决了 React 项目中数据管理的核心问题。它让你的代码更简洁,用户体验更好,开发效率更高。虽然需要一些学习成本,但一旦用起来,你会发现它大大简化了数据获取和状态管理的工作。

如果你的项目中有复杂的数据获取需求,或者你厌倦了自己处理加载状态和缓存逻辑,那么很值得尝试一下 react-query。

本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_13022