TanStack Query — React请求库
本文最后更新于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;

Life's a struggle, I'll conquer it.
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇