使用 React Query 管理状态。〽️

2025-06-08

使用 React Query 管理状态。〽️

React Query是一个大型且完整的库,它简化了客户端向服务器发出请求的工作,甚至执行更多操作。

但是你知道吗?这个库可以用作状态管理器。它可能是 redux-toolkit、zustand 等工具的替代方案。在本文中,我将向你展示如何通过这种方式实现它。

🚨 注意:要理解本文,您应该具备有关如何使用 React Query 的基本知识以及一些 TypeScript 的基本知识。

 

目录。

📌需要用到的技术。📌 创建项目。📌

第一步。📌 创建页面。📌 配置React Query。📌使用 React Query 作为状态管理器。







📌创建函数来 发起请求。📌使用 React Query 获取数据。📌

状态添加新数据。📌状态中移除数据。📌 更新状态数据。





📌结论。

📌演示。📌

代码。

 

📢 要使用的技术。

  • React JS 18.2.0
  • React Query 4.20.4
  • React Router Dom 6.6.1
  • TypeScript 4.9.3
  • Vite JS 4.0.0
  • CSS vanilla(您可以在本文末尾的存储库中找到样式)

📢 创建项目。

我们将为该项目命名:(state-management-rq可选,您可以随意命名)。

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

我们使用 Vite JS 创建项目,并选择 React with TypeScript。

然后运行以下命令导航到刚刚创建的目录。

cd state-management-rq
Enter fullscreen mode Exit fullscreen mode

然后我们安装依赖项。

npm install
Enter fullscreen mode Exit fullscreen mode

然后我们在代码编辑器中打开项目(在我的情况下是 VS 代码)。

code .
Enter fullscreen mode Exit fullscreen mode

📢 第一步。

首先,我们将安装一个 dom 路由器,以便能够在我们的应用程序中创建几个页面。

npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

因此,让我们创建一个src/layout文件夹,用于创建一个非常简单的导航菜单,该菜单将出现在所有页面上。在src/layout
中,我们创建index.tsx文件并添加以下内容:

import { NavLink, Outlet } from 'react-router-dom'

type LinkActive = { isActive: boolean }

const isActiveLink = ({ isActive }: LinkActive) => `link ${isActive ? 'active' : ''}`

export const Layout = () => {
    return (
        <>
            <nav>
                <NavLink className={isActiveLink} to="/">Home 🏠</NavLink>
                <NavLink className={isActiveLink} to="/create">Create ✍️</NavLink>
            </nav>

            <hr className='divider' />

            <div className='container'>
                <Outlet />
            </div>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

然后在src/App.tsx文件中,我们将删除所有内容。然后创建基本路由。

注意:我们将使用createBrowserRouter设置路由,但如果您愿意,您可以使用 react-router-dom 仍然具有的组件(如、、<BrowserRouter/>)代替createBrowserRouter<Routes/><Route/>

通过createBrowserRouter ,我们将创建一个对象来添加路由。请注意,我只有一个父路由,我展示的是导航菜单,而这个路由有 3 个子路由,目前它们尚未创建页面。

最后我们创建默认导出的组件 App,该组件将渲染一个 react-router-dom 组件,该组件<RouterProvider/>接收我们刚刚创建的路由器。

通过它我们可以在不同的路线之间导航。

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './layout';
import { Home } from './pages/home';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <>create</>,
      },
      {
        path: "/create",
        element: <>create</>,
      },
      {
        path: "/:id",
        element: <>edit</>,
      },
    ]
  }
]);

const App = () => ( <RouterProvider router={router} /> )

export default App
Enter fullscreen mode Exit fullscreen mode

然后我们将回到这个文件添加更多内容👀。

📢 创建页面。

现在,我们将为之前定义的路径创建三个页面。
创建一个新文件夹src/pages,并在其中创建 3 个文件。

  1. 主页.tsx

在此文件中,我们仅列出来自 API 的数据,因此目前我们仅放置以下内容:

import { Link } from 'react-router-dom'

export const Home = () => {
    return (
        <>
            <h1>Home</h1>

            <div className="grid">
                <Link to={`/1`} className='user'>
                    <span>username</span>
                </Link>
            </div>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode
  1. 创建用户.tsx

此页面仅用于创建新用户或新数据。因此,我们将创建一个表单。在这种情况下,我不会使用状态来控制表单的输入,而是简单地使用在表单执行 onSubmit 时触发的事件务必将属性名称添加到输入中)。

export const CreateUser = () => {

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        const form = e.target as HTMLFormElement
        const data = Object.fromEntries(new FormData(form))

        // TODO: create new user

        form.reset()
    }

    return (
        <div>
            <h1>Create User</h1>
            <form onSubmit={handleSubmit} className='mt'>
                <input name='user' type="text" placeholder='Add new user' />

                <button>Add User</button>
            </form>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode
  1. editUser.tsx

在此页面中将编辑选定的用户,我们将通过 URL 的参数获取他的 ID,就像我们在创建路由器时建立的那样。

import { useParams } from 'react-router-dom';

export const EditUser = () => {
    const params = useParams()

    const { id } = params

    if (!id) return null

    return (
        <>
            <span>Edit user {id}</span>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

现在我们需要将这些页面放置在路由器中!

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './layout';
import { CreateUser } from './pages/createUser';
import { EditUser } from './pages/editUser';
import { Home } from './pages/home';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "/create",
        element: <CreateUser />,
      },
      {
        path: "/:id",
        element: <EditUser />,
      },
    ]
  }
]);

const App = () => (
    <RouterProvider router={router} />
)

export default App
Enter fullscreen mode Exit fullscreen mode

📢 配置 React Query。

首先我们将安装该库。

npm install @tanstack/react-query
Enter fullscreen mode Exit fullscreen mode

然后我们在src/App.tsx文件中配置提供程序

  1. 首先我们将创建queryClient

在这种情况下,我们将保留这些选项,这将帮助我们将 React Query 用作状态管理器:

  • refetchOnWindowFocus:当您退出应用程序然后返回时,React Query 会返回以发出数据请求。
  • refetchOnMount:当组件重新安装时,它将再次发出请求。
  • retry:重试请求的次数。
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      refetchOnMount: false,
      retry: 1,
    },
  },
});
Enter fullscreen mode Exit fullscreen mode
  1. 然后我们需要导入为我们提供 React Query 的提供程序并向其发送我们刚刚创建的 queryClient。
const App = () => (
  <QueryClientProvider client={queryClient}>
    <RouterProvider router={router} />
  </QueryClientProvider>
)
Enter fullscreen mode Exit fullscreen mode
  1. 最后,虽然它是可选的,但它非常有用,我们将安装 React Query devtools,这将有很大帮助。
npm install @tanstack/react-query-devtools
Enter fullscreen mode Exit fullscreen mode

现在我们将 devtools 放在 React Query 提供程序中。

  1. 该文件最终看起来是这样的。
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { Layout } from './layout';
import { CreateUser } from './pages/createUser';
import { EditUser } from './pages/editUser';
import { Home } from './pages/home';

const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    children: [
      {
        index: true,
        element: <Home />,
      },
      {
        path: "/create",
        element: <CreateUser />,
      },
      {
        path: "/:id",
        element: <EditUser />,
      },
    ]
  }
]);

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      retry: 1
    },
  },
});

const App = () => (
  <QueryClientProvider client={queryClient}>
    <ReactQueryDevtools initialIsOpen={false} />
    <RouterProvider router={router} />
  </QueryClientProvider>
)

export default App
Enter fullscreen mode Exit fullscreen mode

📢 使用 React Query 作为状态管理器。

首先,我们将创建要执行的queryFn 。

📢 创建函数来发出请求。

我们将创建一个文件夹src/api,并创建文件user.ts,其中包含向 API 发出请求的函数。
为了节省创建 API 的时间,我们将使用JSON 占位符,因为它允许我们进行“CRUD”操作,而不仅仅是 GET 请求。

**我们将创建 4 个函数来执行 CRUD。

首先我们设置常量和接口

界面如下:

export interface User {
    id: number;
    name: string;
}
Enter fullscreen mode Exit fullscreen mode

常数为:

import { User } from '../interface';

const URL_BASE = 'https://jsonplaceholder.typicode.com/users'
const headers = { 'Content-type': 'application/json' }
Enter fullscreen mode Exit fullscreen mode
  1. 首先,我们将创建一个函数来请求用户。该函数必须返回一个承诺。
export const getUsers = async (): Promise<User[]> => {
    return await (await fetch(URL_BASE)).json()
}
Enter fullscreen mode Exit fullscreen mode
  1. 然后该函数创建一个新用户,该函数接收一个用户并返回一个解决新用户的承诺。
export const createUser = async (user: Omit<User, 'id'>): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'POST'
    return await (await fetch(URL_BASE, { body, method, headers })).json()
}
Enter fullscreen mode Exit fullscreen mode
  1. 另一个用于编辑用户的函数,它接收要编辑的用户并返回解析已编辑用户的承诺。
export const editUser = async (user: User): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'PUT'
    return await (await fetch(`${URL_BASE}/${user.id}`, { body, method, headers })).json()
}
Enter fullscreen mode Exit fullscreen mode
  1. 最后,一个用于删除用户的函数,它接收一个 id。由于从 API 中删除记录时不会返回任何内容,因此我们将返回一个 Promise,该 Promise 解析该id来识别被删除的用户。
export const deleteUser = async (id: number): Promise<number> => {
    const method = 'DELETE'
    await fetch(`${URL_BASE}/${id}`, { method })
    return id
}
Enter fullscreen mode Exit fullscreen mode

该文件看起来是这样的:

import { User } from '../interface';

const URL_BASE = 'https://jsonplaceholder.typicode.com/users'
const headers = { 'Content-type': 'application/json' }

export const getUsers = async (): Promise<User[]> => {
    return await (await fetch(URL_BASE)).json()
}

export const createUser = async (user: Omit<User, 'id'>): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'POST'
    return await (await fetch(URL_BASE, { body, method, headers })).json()
}

export const editUser = async (user: User): Promise<User> => {
    const body = JSON.stringify(user)
    const method = 'PUT'
    return await (await fetch(`${URL_BASE}/${user.id}`, { body, method, headers })).json()
}

export const deleteUser = async (id: number): Promise<number> => {
    const method = 'DELETE'
    await fetch(`${URL_BASE}/${id}`, { method })
    return id
}
Enter fullscreen mode Exit fullscreen mode

📢 使用 React Query 获取数据。

我们不会将 React Query 代码直接放在组件中,而是将它们一次性放在自定义钩子中,以便将代码集中在一个地方。

因此我们将创建一个文件夹src/hook并在其中创建一个名为useUser.tsx的文件

我们将创建的第一个自定义钩子是useGetUsers ,它只返回useQuery钩子返回的属性

请注意, useQuery需要 2 个参数,一个字符串数组来标识查询,第二个参数是我们之前完成的函数,即从 API 获取用户。

import { useQuery } from '@tanstack/react-query';
import { getUsers } from '../api/user';

const key = 'users'

export const useGetUsers = () => {
    return useQuery([key], getUsers);
}
Enter fullscreen mode Exit fullscreen mode

啊哈拉,我们使用GetUsers。但是,如果我们使用了 useQuery,则需要建立queryKeyqueryFn,这样才能轻松阅读

import { Link } from 'react-router-dom'
import { useGetUsers } from '../hook/useUser'

export const Home = () => {

    const { data, isLoading, isError } = useGetUsers()

    return (
        <>
            <h1>Home</h1>

            {isLoading && <span>fetching a character...</span>}
            {isError && <span>Ups! it was an error 🚨</span>}

            <div className="grid">
                {
                    data?.map(user => (
                        <Link to={`/${user.id}`} key={user.id} className='user'>
                            <span>{user.name}</span>
                        </Link>
                    ))
                }
            </div>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

到目前为止,我们仅设置了数据并将该数据存储在缓存中(它将充当存储状态的存储库),我们尚未在其他地方使用/修改该组件的状态。

📢 向我们的状态添加新数据。

让我们转到src/hooks/useUser.tsx并创建一个新的自定义钩子来创建新用户。

export const useCreateUser = () => {}
Enter fullscreen mode Exit fullscreen mode

在这种情况下以及接下来的情况下,我们将使用useMutation,因为我们将执行 POST 请求来创建新记录。

useMutation 接收要执行的 queryFn,在这种情况下,我们将传递我们创建的用于添加新用户的函数。

export const useCreateUser = () => {
    return useMutation(createUser)
}
Enter fullscreen mode Exit fullscreen mode

我们将传递第二个参数,它是一个对象,它将访问onSuccess属性,该属性是在请求成功时执行的函数。

onSuccess 接收几个参数,我们将使用第一个参数,即createUser函数返回的数据,在本例中必须是新用户。

export const useCreateUser = () => {

return useMutation(createUser, {
        onSuccess: (user: User) => {}
    })
}
Enter fullscreen mode Exit fullscreen mode

现在我们要做的是访问缓存(我们的状态)并添加这个新创建的用户。

对于此任务,我们将使用另一个 React Query 钩子useQueryClient

🚨 注意:不要解构钩子 useQueryClient 的任何属性,因为您将丢失引用,并且该属性将无法按您希望的方式工作。

现在,在onSuccess函数体中,让我们使用setQueryData属性设置新数据

setQueryData,需要2个参数,第一个是queryKey,用于标识要从缓存的哪个部分获取数据并进行修改。

export const useCreateUser = () => {
    const queryClient = useQueryClient();

    return useMutation(createUser, {
        onSuccess: (user: User) => {
            queryClient.setQueryData([key])
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

第二个参数是设置新数据的函数。该函数必须接收缓存中已有的数据,在本例中可以是用户数组,也可以是未定义的数据。

将要进行的将是验证,如果缓存中已经有用户,我们只添加新用户并传播以前的用户,否则我们只返回在数组中创建的用户。

export const useCreateUser = () => {
    const queryClient = useQueryClient();

    return useMutation(createUser, {
        onSuccess: (user: User) => {

            queryClient.setQueryData([key],
                (prevUsers: User[] | undefined) => prevUsers ? [user, ...prevUsers] : [user]
            )

            // queryClient.invalidateQueries([key])
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

🚨 注意:观察前面代码中注释的行

queryClient.invalidateQueries([key])

这行代码用于使缓存无效并重新从服务器请求数据。这通常是你在发出 POST、PUT、DELETE 等请求时需要执行的操作

就我而言,我注释掉了这一行,因为 JSON 占位符 API 不会修改数据,它只是模拟修改数据。所以,如果我发出 DELETE 请求删除一条记录,一切正常,然后我输入invalidateQueries,它会再次返回所有用户,看起来数据没有任何变化。

一旦明确了这一点,我们将使用src/pages/createUser.tsx中的自定义钩子

我们设置了自定义钩子,在这种情况下,您可以解构此钩子返回的道具,但我不会只是为了好玩而这样做(尽管当我们使用多个钩子时,这种语法将是一个很好的选择,以避免与道具的名称冲突)。

    const create_user = useCreateUser()
Enter fullscreen mode Exit fullscreen mode

现在在 handleSubmit 中,我们将访问 mutateAsync 属性,并且由于 TypeScript,我们知道必须传递哪些参数,即新用户的名称。

await create_user.mutateAsync({ name: data.user as string  })
Enter fullscreen mode Exit fullscreen mode

如果你想知道我们从哪里得到这个参数,它来自函数,它来自文件src/>api/user.ts的 createUser 函数,它取决于它作为参数接收的内容,它是我们将作为参数发送的内容。

页面看起来会像这样:

import { useCreateUser } from '../hook/useUser'

export const CreateUser = () => {

    const create_user = useCreateUser()

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        const form = e.target as HTMLFormElement
        const data = Object.fromEntries(new FormData(form))

        await create_user.mutateAsync({ name: data.user as string })

        form.reset()
    }

    return (
        <div>
            <h1>Create User</h1>
            <form onSubmit={handleSubmit} className='mt'>
                <input name='user' type="text" placeholder='Add new user' />
                {create_user.isLoading && <span>creating user...</span>}
                <button>Add User</button>
                {create_user.isSuccess && <span>User created successfully ✅</span>}
                {create_user.isError && <span>Ups! it was an error 🚨</span>}
            </form>
        </div>
    )
}
Enter fullscreen mode Exit fullscreen mode

📢 从状态中删除数据。

现在是时候删除数据了,步骤与我们创建数据时类似。

  • 我们创建自定义钩子useDeleteUser
  • 我们使用useMutation,发送要执行的函数deleteUser
  • 我们访问 onSuccess 属性来执行该函数。
  • 一旦请求成功,我们就使用 useQueryClient 来修改缓存中的数据。
  • 我们将 queryKey 发送到setQueryData属性和函数,我们验证是否有数据,如果有,我们根据从 onSuccess 收到的 ID 过滤数据并排除刚刚删除的用户,返回没有已删除用户的新数组。
export const useDeleteUser = () => {

    const queryClient = useQueryClient();

    return useMutation(deleteUser, {
        onSuccess: (id) => {
            queryClient.setQueryData([key],
                (prevUsers: User[] | undefined) => prevUsers ? prevUsers.filter(user => user.id !== id) : prevUsers
                // queryClient.invalidateQueries([key])
            )
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

我们在src/pages/editUser.tsx中使用自定义钩子

但在我们将要执行的操作分离到不同的组件之前,我们将在同一个文件中创建一个组件,命名为DeleteUser,它接收要删除的用户的 ID。

import { useParams } from 'react-router-dom';
import { useDeleteUser } from '../hook/useUser';
import { User } from '../interface';

export const EditUser = () => {
    const params = useParams()

    const { id } = params

    if (!id) return null

    return (
        <>
            <DeleteUser id={+id} />
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

DeleteUser将具有以下内容。

我们设置自定义钩子useDeleteUser并访问 mutateAsync 方法来执行请求并向其发送 id。

export const DeleteUser = ({ id }: Pick<User, 'id'>) => {
    const delete_user = useDeleteUser()

    const onDelete = async () => {
        await delete_user.mutateAsync(id)
    }

    return (
        <>
            {delete_user.isLoading && <span>deleting user...</span>}

            <button onClick={onDelete}>Delete User</button>

            {delete_user.isSuccess && <span>User deleted successfully ✅, go back home</span>}
            {delete_user.isError && <span>Ups! it was an error 🚨</span>}
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

就是这样,删除后返回主页,你会发现用户已被正确删除。当然,如果你刷新浏览器,这个用户会重新出现,因为我们使用了 JSON 占位符。

📢 更新状态数据。

现在是时候按照相同的步骤更新用户了。

  • 我们创建自定义钩子useEditUser
  • 我们使用useMutation,发送要执行的函数editUser
  • 我们访问 onSuccess 属性来执行该函数。
  • 一旦请求成功,我们就使用 useQueryClient 来修改缓存中的数据。
  • 我们将 queryKey 发送到setQueryData属性和函数,我们验证数据是否存在,如果是,我们通过 ID 识别已修改的用户并分配已修改的新值。
export const useEditUser = () => {
    const queryClient = useQueryClient();

    return useMutation(editUser, {
        onSuccess: (user_updated: User) => {

            queryClient.setQueryData([key],
                (prevUsers: User[] | undefined) => {
                    if (prevUsers) {
                        prevUsers.map(user => {
                            if (user.id === user_updated.id) {
                                user.name = user_updated.name
                            }
                            return user
                        })
                    }
                    return prevUsers
                }
            )
        }
    })
}
Enter fullscreen mode Exit fullscreen mode

现在让我们转到src/pages/editUser.tsx并创建另外两个组件来向您展示一个缺点。
我们创建组件ViewUser来查看用户,以及创建 EditUser来作为编辑用户的表单。

import { useParams } from 'react-router-dom';
import { useDeleteUser, useEditUser, useGetUsers } from '../hook/useUser';
import { User } from '../interface';

export const EditUser = () => {
    const params = useParams()

    const { id } = params

    if (!id) return null

    return (
        <>
            <ViewUser id={+id} />
            <EditUserForm id={+id} />
            <DeleteUser id={+id} />
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

ViewUser接收 id,并使用 useGetUsers 获取所有用户(这不会触发其他请求,但会访问缓存中的用户)。
我们筛选用户并将其显示在屏幕上。

export const ViewUser = ({ id }: Pick<User, 'id'>) => {

    const get_users = useGetUsers()

    const user_selected = get_users.data?.find(user => user.id === +id)

    if (!user_selected) return null

    return (
        <>
            <h1>Edit user: {id}</h1>
            <span>User name: <b>{user_selected?.name}</b></span>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

EditUser ,它也接收一个 ID。实际上,这个组件与createUser.tsx页面中的组件非常相似,你甚至可以重用它,但在本例中我不会这么做。

我们使用自定义钩子useEditUser,访问其方法mutateAsync并传递必要的参数。这样您就可以编辑选定的用户了。


export const EditUserForm = ({ id }: Pick<User, 'id'>) => {

    const edit_user = useEditUser()

    const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault()
        const form = e.target as HTMLFormElement
        const data = Object.fromEntries(new FormData(form))
        await edit_user.mutateAsync({ name: data.user as string, id })
        form.reset()
    }

    return (
        <>
            <form onSubmit={handleSubmit}>
                <input name='user' type="text" placeholder='Update this user' />
                {edit_user.isLoading && <span>updating user...</span>}
                <button>Update User</button>
                {edit_user.isSuccess && <span>User updated successfully ✅</span>}
                {edit_user.isError && <span>Ups! it was an error 🚨</span>}
            </form>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

但请注意,您会注意到,当正确更新用户时,ViewUser组件不会被渲染,也就是说,它保留了前一个用户的名称值。但是,如果您返回主页,您会注意到用户的名称已更新。

这是因为需要新的渲染来更改ViewUser组件。

对此我想到了一个解决方案。创建一个新的自定义钩子来处理可观察对象,并感知缓存中某个部分的变化。

在这个自定义钩子中,我们将使用另一个自定义钩子useGetUsers和钩子useQueryClient

  1. 首先,我们使用 useGetUsers 并返回其 props,但我们覆盖了 prop 数据,因为我们必须注意它的变化。
export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    return {
      ...get_users,
        data:[],
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. 我们创建一个状态来管理用户数组,并将该状态分配给 prop 数据。
export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    const [users, setUsers] = useState<User[]>()

    return {
        ...get_users,
        data: users,
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. 我们使用缓存中现有的数据初始化状态,如果缓存中没有数据,则返回一个空数组。这是使用钩子useQueryClient及其属性getQueryData实现的。
export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    const queryClient = useQueryClient()

    const [users, setUsers] = useState<User[]>(() => {

        const data = queryClient.getQueryData<User[]>([key])
        return data ?? []
    })

    return {
        ...get_users,
        data: users,
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. 现在我们将使用 effect 来处理观察者。在 effect 中,我们创建一个 QueryObserver 的新实例,它需要两个参数:queryClient和一个需要queyKey 的对象,该对象用于确定要监视缓存的哪个部分。
useEffect(() => {
    const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })

}, [])
Enter fullscreen mode Exit fullscreen mode
  1. 现在我们需要订阅观察者,因此我们执行观察者的subscribe属性。subscribe 会接收一个回调函数,该回调函数返回一个对象,该对象与返回类似useQuery钩子函数的属性基本相同。因此,我们会验证 data 属性中是否有数据,然后使用新数据更新状态。
useEffect(() => {
    const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })

    const unsubscribe = observer.subscribe(result => {
        if (result.data) setUsers(result.data)
    })

}, [])
Enter fullscreen mode Exit fullscreen mode
  1. 请记住,一个好的做法是在拆卸组件时取消订阅。
useEffect(() => {
    const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })

    const unsubscribe = observer.subscribe(result => {
        if (result.data) setUsers(result.data)
    })

    return () => {
        unsubscribe()
    }
}, [])
Enter fullscreen mode Exit fullscreen mode

这个新的自定义钩子看起来是这样的。


export const useGetUsersObserver = () => {

    const get_users = useGetUsers()

    const queryClient = useQueryClient()

    const [users, setUsers] = useState<User[]>(() => {

        const data = queryClient.getQueryData<User[]>([key])
        return data ?? []
    })


    useEffect(() => {
        const observer = new QueryObserver<User[]>(queryClient, { queryKey: [key] })

        const unsubscribe = observer.subscribe(result => {
            if (result.data) setUsers(result.data)
        })

        return () => {
            unsubscribe()
        }
    }, [])

    return {
        ...get_users,
        data: users,
    }
}
Enter fullscreen mode Exit fullscreen mode

现在,我们只需要在需要获取这些数据的组件中使用它即可。例如在ViewUser组件中。

不要忘记导入useGetUsersObserver

export const ViewUser = ({ id }: Pick<User, 'id'>) => {

    // const get_users = useGetUsers()
    const get_users = useGetUsersObserver()

    const user_selected = get_users.data?.find(user => user.id === +id)

    if (!user_selected) return null

    return (
        <>
            <h1>Edit user: {id}</h1>
            <span>User name: <b>{user_selected?.name}</b></span>
        </>
    )
}
Enter fullscreen mode Exit fullscreen mode

现在,如果您尝试更新数据或删除数据,您将看到一旦请求成功,ViewUser组件也将如何更新。

这样,我们将使用 React Query 缓存作为状态管理器来完成 CRUD。

📢结论。

React Query 是一个非常强大的库,它确实能帮助我们处理请求。但现在你可以进一步扩展它,将它用作状态管理器,这或许是另一种选择。

我希望你喜欢这篇文章,也希望我能帮助你扩展对 React Query 的了解。

如果您知道有关如何使用 React Query 管理状态的任何其他不同或更好的方法,请随时在评论中告诉我。

如果您有兴趣就某个项目与我联系,欢迎查看我的投资组合!富兰克林·马丁内斯·卢卡斯

🔵 别忘了在推特上关注我:@Frankomtz361

📢 演示。

https://rq-state-management.netlify.app/

📢 源代码。

https://github.com/Franklin361/state-management-react-query

鏂囩珷鏉ユ簮锛�https://dev.to/franklin030601/managing-state-with-react-query-1842
PREV
将 Zusand 与 React JS 结合使用!🚀
NEXT
将 Laravel 11 应用 Docker 化