本文最后更新于238 天前,其中的信息可能已经过时,如有错误请发送邮件到1986413837@qq.com
TanStack Query的核心作用:
让前端应用的数据获取、缓存和同步变得极其简单,同时保持高性能和一致性
自动管理 服务器状态(Server State),处理 数据获取、缓存、更新、同步和错误处理,让你只需关心 “要什么数据” 和 “如何修改数据”,而不用手动写复杂的状态逻辑
Without TanStack Query
import { useEffect, useState } from "react";
const WithoutTanstackQuery = () => {
const [id, setId] = useState(1);
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let rc = false
const handleFetch = async () => {
setIsLoading(true);
setError(null);
try {
const res = await fetch(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
if (rc) return;
if (!res.ok) throw new Error(`Error fetching data: ${res.statusText}`);
const result = await res.json();
setData(result);
} catch (e: any) {
setError(e.message);
} finally {
setIsLoading(false);
}
};
handleFetch();
return () => {
rc = true
}
}, [id]);
return (
<div>
{isLoading && <h1>Loading...</h1>}
{error && <h1>{error}</h1>}
{data && <h1>{JSON.stringify(data)}</h1>}
<button
className="bg-black text-white p-[3rem] rounded-2xl"
onClick={() => setId((prevId) => prevId + 1)}
>
Next
</button>
</div>
);
};
export default WithoutTanstackQuery;
With TanStack Query
main.tsx
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
import { QueryClientProvider, QueryClient } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
const queryClient = new QueryClient()
createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools initialIsOpen={false}></ReactQueryDevtools>
</QueryClientProvider>
)
WithTanStackQuery.tsx
import { useQuery } from "@tanstack/react-query"
import axios from "axios"
const fetchData = async () => {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos"
)
return response.data
}
const WithTanStackQuery = () => {
const { data, error, isLoading } = useQuery({
queryKey: ['todo'],
queryFn: fetchData
})
if (isLoading) {
return <h1>Loading...</h1>
}
if (error) {
return <p>An Error Occurerd! :{error.message}</p>
}
return (
<div>
<h1>Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
export default WithTanStackQuery
Deduplication
在同一个时间点,对完全相同的多个数据请求,TanStack Query 会自动将它们合并为单个网络请求
import { useQuery } from "@tanstack/react-query"
const getRandomNumber = async () => {
return Promise.resolve(Math.random())
}
const Deduplication = () => {
const { data } = useQuery({ queryKey: ['randomNumber'], queryFn: getRandomNumber })
return (
<div>
RandomNumber is : {data}
</div>
)
}
export default Deduplication
App.tsx
function App() {
return (
<>
<div className="">
<Deduplication></Deduplication>
<Deduplication></Deduplication>
<Deduplication></Deduplication>
</div>
</>
)
}
export default App
会获取相同的随机数!
StaleTime
import { useQuery } from "@tanstack/react-query"
import axios from "axios"
const fetchData = async () => {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/todos"
)
return response.data
}
const StaleTime = () => {
const { data, error, isLoading } = useQuery({
queryKey: ['todo'],
queryFn: fetchData,
staleTime: 5000
})
if (isLoading) {
return <h1>Loading...</h1>
}
if (error) {
return <p>An Error Occurerd! :{error.message}</p>
}
return (
<div>
<h1>Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
export default StaleTime

五秒后

RefetchInterval
自动按设定的时间间隔(毫秒)重新执行查询,保持数据最新
import { useState, useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
const fetchTodo = async (id: number) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos/${id}`
);
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const RefetchInterval = () => {
const [currentId, setCurrentId] = useState(1);
const { data, error, isLoading } = useQuery({
queryKey: ["todo", currentId],
queryFn: () => fetchTodo(currentId),
refetchInterval: 5000,
});
useEffect(() => {
const interval = setInterval(() => {
setCurrentId((prevId) => (prevId < 200 ? prevId + 1 : 1));
}, 5000);
return () => clearInterval(interval);
}, []);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>An error occurred: {error.message}</div>;
return (
<div>
<h1>Todo</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
<button
onClick={() =>
setCurrentId((prevId) => (prevId < 200 ? prevId + 1 : 1))
}
>
Next Todo
</button>
</div>
);
};
export default RefetchInterval;
MutatingData
TanStack Query 中的 useMutation是专门用于处理数据变更操作(如创建、更新、删除)的核心 Hook
与 useQuery不同,它不涉及缓存管理,而是专注于处理副作用操作
import { useState } from "react";
import { useMutation, useQueryClient } from "@tanstack/react-query";
interface Todo {
id?: number;
title: string;
completed: boolean;
}
const postTodo = async (newTodo: Todo): Promise<Todo> => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(newTodo),
});
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const MutateData = () => {
const queryClient = useQueryClient();
const [title, setTitle] = useState("");
const { mutate, error, status } = useMutation<Todo, Error, Todo>({
mutationFn: postTodo,
onSuccess: () => {
// ✅ 正确用法:传递 { queryKey: [...] }
queryClient.invalidateQueries({ queryKey: ["todos"] });
},
onError: (error) => {
console.error("Error posting todo:", error);
},
});
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (title.trim() === "") return;
mutate({ title, completed: false });
setTitle("");
};
return (
<div>
<h1>Create New Todo</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Enter todo title"
/>
<button type="submit" disabled={status === "pending"}>
{status === "pending" ? "Adding..." : "Add Todo"}
</button>
{error && <div>An error occurred: {error.message}</div>}
{status === "success" && <div>Todo added successfully!</div>}
</form>
</div>
);
};
export default MutateData;
为什么需要 invalidateQueries?
- 假设你的应用有一个
useQuery(["todos"], fetchTodos),用于获取所有待办事项 - 当你新增一个 Todo(
POST请求)后,后端数据已经变化,但前端缓存仍然是旧数据 - 如果不手动更新缓存,用户看到的列表 不会立即包含新增的 Todo(除非手动刷新页面)
FetchFromMutipleEndpoints
import { useState } from "react";
import { useQueries } from "@tanstack/react-query";
const fetchTodos = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/todos");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const fetchPosts = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const FetchFromMultipleEndpoints = () => {
const [currentTodoId, setCurrentTodoId] = useState(1);
const [currentPostId, setCurrentPostId] = useState(1);
// Using useQueries to fetch from multiple endpoints
const results = useQueries({
queries: [
{
queryKey: ["todos"],
queryFn: fetchTodos,
},
{
queryKey: ["posts"],
queryFn: fetchPosts,
},
],
});
console.log(results)
const [todosQuery, postsQuery] = results;
if (todosQuery.isLoading || postsQuery.isLoading)
return <div>Loading...</div>;
if (todosQuery.error || postsQuery.error)
return (
<div>
An error occurred:
{todosQuery.error?.message || postsQuery.error?.message}
</div>
);
const todosData = todosQuery.data;
const postsData = postsQuery.data;
// Handle button clicks to fetch specific todos and posts
const handleNextTodoClick = () => {
setCurrentTodoId((prevId) => Math.min(prevId + 1, todosData.length));
};
const handleNextPostClick = () => {
setCurrentPostId((prevId) => Math.min(prevId + 1, postsData.length));
};
return (
<div>
<h1>Todos</h1>
<pre>
{JSON.stringify(
todosData.find((todo: any) => todo.id === currentTodoId),
null,
2
)}
</pre>
<button onClick={handleNextTodoClick}>Next Todo</button>
<h1>Posts</h1>
<pre>
{JSON.stringify(
postsData.find((post: any) => post.id === currentPostId),
null,
2
)}
</pre>
<button onClick={handleNextPostClick}>Next Post</button>
</div>
);
};
export default FetchFromMultipleEndpoints;
Pagination
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
const fetchTodos = async (page = 1, limit = 10) => {
const response = await fetch(
`https://jsonplaceholder.typicode.com/todos?_page=${page}&_limit=${limit}`
);
if (!response.ok) throw new Error("Network response was not ok");
return response.json();
};
const Pagination = () => {
const [currentPage, setCurrentPage] = useState(1);
const pageSize = 10;
const { data, error, isLoading } = useQuery({
queryKey: ["todos", currentPage],
queryFn: () => fetchTodos(currentPage, pageSize),
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>An error occurred: {error.message}</div>;
const handleNextPage = () => {
setCurrentPage((prevPage) => prevPage + 1);
};
const handlePreviousPage = () => {
setCurrentPage((prevPage) => Math.max(prevPage - 1, 1));
};
return (
<div>
<h1>Todos</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
<div>
<button onClick={handlePreviousPage} disabled={currentPage === 1}>
Previous Page
</button>
<button onClick={handleNextPage}>Next Page</button>
</div>
</div>
);
};
export default Pagination;