小伙伴们,是时候开始 react Query 之旅了。你还不知道这个库吗?完美,你来对地方了
React Query 是什么?React Query 是由@TannerLinsley 创建的 npm 库。它是一个针对 React 应用的状态管理器,可以简化许多任务,例如处理 HTTP 请求状态、在客户端保存数据以防止多次请求、使用 hooks 共享数据等等。
你将在本系列中发现更多关于它的内容,学习如何使用它,并欣赏其在 React 应用程序中的简洁性。
第一个核心概念是 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 的名称和其概念。
接下来,你将学习如何调试和检查 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 类似,但有两个新配置需要了解。
现在您具备了身份验证流程的所有块,但是现在是将 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
翻译 / 润色:ssh
如今的 Web 前端已被 React、Vue 和 Angular 三分天下,尽管现在的 jQuery 已不再那么流行,但 jQuery 的设计思想还是非常值得致敬和学习的,特别是 jQuery 的插件化。
受控组件与非受控组件在官网与国内网上的资料都不多,有些人觉得它可有可不有,也不在意。这恰恰显示React的威力,满足不同规模大小的工程需求。
一般在传统模式下,我们构建前端项目很简单。就是下载各种js文件,如JQuery、Echart等,直接放置在html静态文件。Webpack则是JavaScript中比较知名的打包工具。这两个构建工具构成了React应用快速搭建的基础。
Gatsby能快速的使用 React 生态系统来生成静态网站,可以结合React Component、Markdown 和服务端渲染来完成静态网站生成让他更强大。
React推出后,出于不同的原因先后出现三种定义react组件的方式,殊途同归;具体的三种方式:函数式定义的无状态组件、es5原生方式React.createClass定义的组件、es6形式的extends React.Component定义的组件
React主要思想是通过构建可复用组件来构建用户界面,每个组件都有自己的生命周期,它规定了组件的状态和方法需要在哪个阶段改变和执行。所谓组件就是有限状态机,,表示有限个状态以及在这些状态之间的转移和动作行为的模型。
React 相关的优化:使用 babel-react-optimize 对 React 代码进行优化,检查没有使用的库,去除 import 引用,按需打包所用的类库,比如 lodash 、echarts 等.Webpack 构建打包存在的问题两个方面:构建速度慢,打包后的文件体积过大
这篇文章主要介绍React Router定义路由之后如何传值,有关React和React Router 。react router中页面传值的三种方法:props.params、query、state
react 高阶组件简单的理解是:一个包装了另一个基础组件的组件。高阶组件的两种形式:属性代理(Props Proxy)、反向继承 (Inheritance Inversion)
React 支持一种非常特殊的属性 Ref ,你可以用来绑定到 render() 输出的任何组件上。这个特殊的属性允许你引用 render() 返回的相应的支撑实例( backing instance )。这样就可以确保在任何时间总是拿到正确的实例
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!