React Query 完全指南,时下最热辣的请求库!

简介: 小伙伴们,是时候开始 React Query 之旅了。你还不知道这个库吗?完美,你来对地方了

小伙伴们,是时候开始 React Query 之旅了。你还不知道这个库吗?完美,你来对地方了 😁


介绍


React Query 是什么?React Query 是由@TannerLinsley 创建的 npm 库。它是一个针对 React 应用的状态管理器,可以简化许多任务,例如处理 HTTP 请求状态、在客户端保存数据以防止多次请求、使用 hooks 共享数据等等。

你将在本系列中发现更多关于它的内容,学习如何使用它,并欣赏其在 React 应用程序中的简洁性。


useQuery


第一个核心概念是 useQuery。通过它,你可以以一种非常简单的方式从源中检索数据并处理此请求的所有状态。

让我们看一个例子:

import { useQuery } from '@tanstack/react-query';
const fetchTodos = async (): Promise<Todo[]> => {
  const response = await fetch('api/tasks');
  if (!response.ok) {
    throw new ResponseError('Failed to fetch todos', response);
  }
  return await response.json();
};
export const useTodos = (): UseTodos => {
  const {
    data: todos = [],
    isLoading,
    isFetching,
    error,
  } = useQuery(['todos'], fetchTodos, {
    refetchOnWindowFocus: false,
    retry: 2,
  });
  ...
};

在这个例子中,你可以看到 useQuery 的要点。

UseQuery 是一个 React hook,它需要三个参数:

1.查询关键字

2.查询函数

3.配置项

让我们从第一个参数开始。查询关键字是 React Query 用于识别你的查询的关键字。通过该关键字,React Query 能够存储结果并在应用程序的不同部分中使用它。该关键字用于标识查询,你还可以使用 React Query 客户端通过代码重置查询或更改值。

查询函数是用于从源(rest、GraphQL 等等)检索数据的方法。它很简单,一个返回某种数据的函数,可以是简单函数或者大多数情况下是一个 promise。

然后是配置项,这些很简单啦 :) 有许多可能的选项用于以不同的方式运行查询(重试次数、何时刷新数据、如何缓存数据等等..)。

这个 hook 的结果有三个重要的属性:

  • data:此属性包含查询函数的结果。请注意数据也可能为 undefined;这是因为在第一次调用时,当请求处于等待状态时,data 尚未呈现。
  • isLoading:这个标志表示 React Query 正在加载数据。还有一个 isFetching 标志,如果你正在创建无限滚动,则很重要。isFetching 标志表示有一个挂起的请求,如果应用程序请求下一个信息,这是非常完美的。
  • error:此对象包含请求存在问题的错误;通过使用它,你可以获取错误并为用户创建漂亮的信息提示。

好的,你现在对 useQuery 的工作方式及其潜力有了一个概念,但是如果你更有兴趣,可以观看我的视频了解更多信息。

好的,就这些!我很快会回到你呈现 React Query 的另一个功能。希望你喜欢这份内容。


突变


伙计们,是时候谈论 React Query 中的第二个核心概念了,即突变。

这是什么?

突变是用户可以在你的应用程序中执行的操作,你可以将突变想象成更改或创建某些东西的操作。

为了更好地在代码中理解突变是什么,让我们从一个代码片段开始

import { useMutation } from '@tanstack/react-query';
const postTodo = async (text: Todo['text']): Promise<Todo> => {
  const response = await fetch('api/tasks', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ text }),
  });
  if (!response.ok) {
    throw new ResponseError('Failed to insert new todo', response);
  }
  return await response.json();
};
export const useAddTodo = (): UseAddTodo => {
  const { mutate: addTodo, isLoading, error } = useMutation(postTodo, {
    onSuccess: () => {
      // Success actions
    },
    onError: (error) => {
      // Error actions
    },
  });
  return {
    addTodo,
  };
};

正如你所看到的,突变是一个简单的 hook,有两个参数:

  • 用于处理请求的函数
  • 用于处理成功和错误 hooks 的选项,但也用于配置突变(重试、重试延迟等)。

结果有三个主要的对象:

  • mutate:这是在你的代码中运行突变的操作
  • isLoading:这个标志表示突变是否正在进行
  • error:这表示如果请求出现错误,则显示错误

在 React 应用程序中使用突变,你可以处理所有那些操作来改变数据并简化这些请求的状态管理。

当你处理突变时,另一个重要的概念是 QueryClient。

使用 QueryClient,你可以使已经提供的查询失效,并告诉 React Query 重新请求数据,因为你可以确保在突变之后,那些数据还不是有效的。

为了这样做,你必须使用 useQueryClient 钩子来检索 queryClient,并使用 invalidateQueries 方法,你可以使 React Query 缓存无效,同时使指定的查询或多个查询失效。

以下是一个例子

import { useMutation, useQueryClient } from '@tanstack/react-query';
import { QUERY_KEY } from '../../../../constants/queryKeys';
export const useAddTodo = (): UseAddTodo => {
  const client = useQueryClient();
  const { mutate: addTodo } = useMutation(postTodo, {
    onSuccess: () => {
      client.invalidateQueries([QUERY_KEY.todos]);
    },
  });
  ...
};

好的,我想你已经对如何使用 useMutation 和 useQueryClient 有了一个概念,但是如果你想深入了解它们,请别忘了看我的 Youtube 视频。

React Query 提供的两个 hooks:useIsFetching 和 useIsMutation。

这些 hooks 可用于了解应用程序中是否存在获取请求或突变请求正在进行。

如果需要创建一个全局的加载器,在存在一个或多个请求进行时出现,它们就会很有用。

但是你如何使用它们呢?

我们先从 useIsFetching 开始。

import { useIsFetching } from '@tanstack/react-query';
export default function Loader() {
  const isFetching = useIsFetching();
  if (!isFetching) return null;
  return <>Fetching...</>
}

正如你所看到的,语法非常简单。你可以从库中导入该 hook 并在组件中使用。该 hook 仅返回一个布尔值,表示应用程序中是否存在一个或多个获取请求。因此,你可以根据这些数据决定是否显示加载器。Easy peasy!

现在是时候移动到 useIsMutation hook 了。这个 hook 类似于之前的那个,唯一不同的概念是这个 hook 处理的是突变请求。让我们看一个例子!

import { useIsMutating } from '@tanstack/react-query';
export default function Loader() {
  const isMutating = useIsMutating();
  if (!isMutating) return null;
  return <>Mutating...</>
}

正如你所注意到的那样,语法与之前的相同,唯一不同的是 hook 的名称和其概念。


Dev tool


接下来,你将学习如何调试和检查 React Query 应用程序中发生的一切。当你开始学习或使用一个工具时,检查它周围的工具以了解开发者体验是很正常的,这样你就可以决定是否继续使用它。React Query 团队知道这一点,并决定构建一个工具来帮助那些想要使用 React Query 进行工作的开发者。

这个工具叫做react-query-devtools,你只需要通过一个简单的步骤安装它。

打开你的终端并输入

$ npm i @tanstack/react-query-devtools

现在,在你的项目中,你可以使用它并得到所有需要调试你的应用程序所需的信息。

这个工具很容易使用。在你的应用程序中,你必须将它导入并在你渲染ReactQueryProvider的地方渲染它。

import { QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import React from "react";
import { queryClient } from './react-query/client';
import Router from './Router';
function App() {
  return (
    <React.StrictMode>
      <QueryClientProvider client={queryClient}>
        ...
        <ReactQueryDevtools />
      </QueryClientProvider>
    </React.StrictMode>
  );
}
export default App;

Easy peasy, no? 😎

使用ReactQueryDevtools,你不需要关注环境是否渲染该组件,因为它默认提供了它。它仅在条件process.env.NODE_ENV === 'development'为 true 时才渲染该组件。

如果需要,你可以自定义该组件或强制在生产模式下渲染它。要了解更多相关主题,请查阅文档。

在你的应用程序中使用该组件的好处在于,它允许在运行时查看 ReactQuery 中发生的情况。你可以检查状态中保存的数据,不同的查询有多少应用程序部分使用等等。你也可以重置状态或删除部分状态以重新获取数据。

没错,它提供了许多很好的功能来调试和检查你的 React Query 应用程序,并且它是每个使用 React Query 的开发者的好工具。在这里,你可以找到一个 ReactQueryDevtool 的示例。


权限


每个应用程序都应该处理认证流程;在这篇文章中,你将学习如何使用 React Query 在你的 React 应用程序中构建认证流程。


注册


构建认证流程的第一步是注册操作。通过本系列你已经学习到,你应该构建一个 mutation 来执行此操作。一种可能的解决方法如下:

async function signUp(email: string, password: string): Promise<User> {
  const response = await fetch('/api/auth/signup', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email, password })
  })
  if (!response.ok)
    throw new ResponseError('Failed on sign up request', response);
  return await response.json();
}
type IUseSignUp = UseMutateFunction<User, unknown, {
  email: string;
  password: string;
}, unknown>
export function useSignUp(): IUseSignUp {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();
  const { mutate: signUpMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signUp(email, password), {
    onSuccess: (data) => {
      // TODO: save the user in the state
      navigate('/');
    },
    onError: (error) => {
      enqueueSnackbar('Ops.. Error on sign up. Try again!', {
        variant: 'error'
      });
    }
  });
  return signUpMutation;
}

通过创建这样的 mutation,你可以非常简单和清晰地构建一个注册操作。

现在使用 useSignUp hook,你可以获取 mutation 并调用 signUp 请求在你的系统中创建新用户。正如你可以看到的,代码非常简单,signUp 方法调用 API 来发布新用户的数据并返回保存在数据库中的用户数据。然后使用 useMutation hook,可以构建处理 signUp 操作的 mutation。如果一切正常,onSuccess hook 调用导航到主页;否则,onError hook 显示一个错误的提示。

在代码中,有一个 TODO 表示缺失的内容;我们将在此后的文章中回到这行代码。


登录


如果你正在建立一个身份验证流程,那么 SignIn 是构建的第二个步骤。在这种情况下,SignIn 与 SignUp 非常相似;唯一变化的是终点和 Hook 的范围。

所以代码可以是这样的:

async function signIn(email: string, password: string): Promise<User> {
  const response = await fetch('/api/auth/signin', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ email, password })
  })
  if (!response.ok)
    throw new ResponseError('Failed on sign in request', response);
  return await response.json();
}
type IUseSignIn = UseMutateFunction<User, unknown, {
  email: string;
  password: string;
}, unknown>
export function useSignIn(): IUseSignIn {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();
  const { mutate: signInMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signIn(email, password), {
    onSuccess: (data) => {
      // TODO: save the user in the state
      navigate('/');
    },
    onError: (error) => {
      enqueueSnackbar('Ops.. Error on sign in. Try again!', {
        variant: 'error'
      });
    }
  });
  return signInMutation;
}


用户


身份验证流程的核心部分是将用户保存在状态中。为了做到这一点,在这种情况下,最好的方法是创建一个称为 useUser 的新 hook,它是用户数据的所有者。

useUser hook 必须具有用户数据,并且它必须将用户数据保存在本地存储中,并在以后刷新页面或返回时检索它们。

先从处理本地存储的代码开始,通常使用具有特定目标的小功能创建此代码,例如:

import { User } from './useUser';
const USER_LOCAL_STORAGE_KEY = 'TODO_LIST-USER';
export function saveUser(user: User): void {
  localStorage.setItem(USER_LOCAL_STORAGE_KEY, JSON.stringify(user));
}
export function getUser(): User | undefined {
  const user = localStorage.getItem(USER_LOCAL_STORAGE_KEY);
  return user ? JSON.parse(user) : undefined;
}
export function removeUser(): void {
  localStorage.removeItem(USER_LOCAL_STORAGE_KEY);
}

以这种方式,您可以创建一个处理用户的所有本地存储函数的小模块。

现在是时候看看如何构建 useUser hook 了。

先从以下代码开始:

async function getUser(user: User | null | undefined): Promise<User | null> {
  if (!user) return null;
  const response = await fetch(`/api/users/${user.user.id}`, {
    headers: {
      Authorization: `Bearer ${user.accessToken}`
    }
  })
  if (!response.ok)
    throw new ResponseError('Failed on get user request', response);
  return await response.json();
}
export interface User {
  accessToken: string;
  user: {
    email: string;
    id: number;
  }
}
interface IUseUser {
  user: User | null;
}
export function useUser(): IUseUser {
  const { data: user } = useQuery<User | null>(
    [QUERY_KEY.user],
    async (): Promise<User | null> => getUser(user),
    {
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      initialData: userLocalStorage.getUser,
      onError: () => {
        userLocalStorage.removeUser();
      }
    });
  useEffect(() => {
    if (!user) userLocalStorage.removeUser();
    else userLocalStorage.saveUser(user);
  }, [user]);
  return {
    user: user ?? null,
  }
}

getUser 函数很简单,它提供获取用户信息的 HTTP 请求;如果用户为空,则返回 null,否则调用 HTTP 终点。

useQuery hook 与之前看到的其他 hook 类似,但有两个新配置需要了解。

  • refetchOnMount:此选项很重要,可防止 hook 每次使用时重新加载数据
  • initialData:此选项用于从本地存储加载数据;initialData 接受一个返回初始值的函数;如果初始值已定义,则 React Query 使用该值刷新数据。

现在您具备了身份验证流程的所有块,但是现在是将 useSignUp 和 useSignIn 与 useUser hook 链接起来的时候了。

使用 QueryClient,您可以使用 setQueryData 函数设置特定查询的数据。

因此,以以下方式更改以前的 TODOs 注释:

export function useSignUp(): IUseSignUp {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();
  const { mutate: signUpMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signUp(email, password), {
    onSuccess: (data) => {
      queryClient.setQueryData([QUERY_KEY.user], data);
      navigate('/');
    },
    onError: (error) => {
      enqueueSnackbar('Ops.. Error on sign up. Try again!', {
        variant: 'error'
      });
    }
  });
  return signUpMutation;
}
export function useSignIn(): IUseSignIn {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const { enqueueSnackbar } = useSnackbar();
  const { mutate: signInMutation } = useMutation<User, unknown, { email: string, password: string }, unknown>(
    ({
      email,
      password
    }) => signIn(email, password), {
    onSuccess: (data) => {
      queryClient.setQueryData([QUERY_KEY.user], data);
      navigate('/');
    },
    onError: (error) => {
      enqueueSnackbar('Ops.. Error on sign in. Try again!', {
        variant: 'error'
      });
    }
  });
  return signInMutation;
}

只需两行简单的代码,您就可以将用户设置到 useUser 状态中,因为设置查询数据的键与 useUser 相同。

然后,使用 useUser hook 中的 useEffect,可以在用户更改时删除或设置用户数据到本地存储中:

export function useUser(): IUseUser {
  const { data: user } = useQuery<User | null>(
    [QUERY_KEY.user],
    async (): Promise<User | null> => getUser(user),
    {
      refetchOnMount: false,
      refetchOnWindowFocus: false,
      refetchOnReconnect: false,
      initialData: userLocalStorage.getUser,
      onError: () => {
        userLocalStorage.removeUser();
      }
    });
  useEffect(() => {
    if (!user) userLocalStorage.removeUser();
    else userLocalStorage.saveUser(user);
  }, [user]);
  return {
    user: user ?? null,
  }
}

要完成身份验证流程,唯一缺少的是注销。

可以使用一个名为 useSignOut 的自定义 hook 来构建它;它的实现很简单,如下所示:

import { useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { QUERY_KEY } from '../constants/queryKeys';
type IUseSignOut = () => void
export function useSignOut(): IUseSignOut {
  const queryClient = useQueryClient();
  const navigate = useNavigate();
  const onSignOut = useCallback(() => {
    queryClient.setQueryData([QUERY_KEY.user], null);
    navigate('/auth/sign-in');
  }, [navigate, queryClient])
  return onSignOut;
}

正如您可以注意到的那样,hook 返回一个简单的函数,该函数清除用户状态中的值并导航到登录页面。

好的,完美。现在您已具备使用 React Query 构建身份验证流程的所有知识!

原文:https://dev.to/this-is-learning/react-query-usequery-36i

相关文章
|
1月前
|
前端开发 JavaScript UED
React 图标库使用指南
本文详细介绍如何在 React 项目中使用 `react-icons` 等图标库,涵盖环境搭建、基础使用、常见问题与易错点、高级用法等内容,并通过代码案例进行说明。适合初学者和进阶开发者参考。
52 8
|
2月前
|
前端开发 JavaScript API
React开发需要了解的10个库
本文首发于微信公众号“前端徐徐”,介绍了React及其常用库。React是由Meta开发的JavaScript库,用于构建动态用户界面,广泛应用于Facebook、Instagram等知名网站。文章详细讲解了Axios、Formik、React Helmet、React-Redux、React Router DOM、Dotenv、ESLint、Storybook、Framer Motion和React Bootstrap等库的使用方法和应用场景,帮助开发者提升开发效率和代码质量。
136 4
React开发需要了解的10个库
|
3月前
|
前端开发 JavaScript UED
react或者vue更改用户所属组,将页面所有数据进行替换(解决问题思路)____一个按钮使得页面所有接口重新请求
在React或Vue中,若需在更改用户所属组后更新页面所有数据但不刷新整个页面,可以通过改变路由出口的key值来实现。在用户切换组成功后,更新key值,这会触发React或Vue重新渲染路由出口下的所有组件,从而请求新的数据。这种方法避免了使用`window.location.reload()`导致的页面闪烁,提供了更流畅的用户体验。
59 1
react或者vue更改用户所属组,将页面所有数据进行替换(解决问题思路)____一个按钮使得页面所有接口重新请求
|
2月前
|
资源调度 前端开发 JavaScript
React中classnames库使用
【10月更文挑战第7天】
|
1月前
|
资源调度 前端开发 JavaScript
React 测试库 React Testing Library
【10月更文挑战第22天】本文介绍了 React Testing Library 的基本概念和使用方法,包括安装、基本用法、常见问题及解决方法。通过代码案例详细解释了如何测试 React 组件,帮助开发者提高应用质量和稳定性。
53 0
|
3月前
|
前端开发
React技术栈-react使用的Ajax请求库实战案例
这篇文章介绍了在React应用中使用Axios和Fetch库进行Ajax请求的实战案例,展示了如何通过这些库发送GET和POST请求,并处理响应和错误。
62 10
React技术栈-react使用的Ajax请求库实战案例
|
3月前
|
前端开发
React技术栈-react使用的Ajax请求库用户搜索案例
这篇文章展示了一个React技术栈中使用Ajax请求库(如axios)进行用户搜索的实战案例,包括React组件的结构、状态管理以及如何通过Ajax请求获取并展示GitHub用户数据。
37 7
React技术栈-react使用的Ajax请求库用户搜索案例
|
3月前
|
前端开发
React页面跳转取消上一个页面的所有请求
React页面跳转时取消上一个页面的所有axios请求,通过axios拦截器设置cancelToken,并在页面跳转时调用cancel函数取消未完成的请求。
51 2
|
3月前
|
前端开发 JavaScript
React配合axios请求拦截校验session,403跳转至登陆页面
React中使用axios进行请求拦截,通过自定义事件监听和响应拦截实现403状态码时的自动登录页面跳转。
109 2
|
4月前
|
存储 前端开发 JavaScript