每个应用程序都应该处理身份验证流程;在本文中,您将了解如何使用 React Query 在 React 应用程序中构建身份验证流程。
报名
构建身份验证流程的第一步是注册操作。正如您在本系列中已经了解到的那样,您应该构建一个变异来执行此操作。一个可能的解决方案可能是这个
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
}
通过创建这样的突变,您可以以一种非常简单明了的方式构建注册。
现在使用useSignUp钩子,您可以获得突变并调用注册请求以在您的系统中创建一个新用户。正如您所注意到的,代码非常简单;该signUp方法调用 API 发布新用户的数据并返回保存在数据库中的用户数据。然后使用useMutation挂钩,您可以构建突变来处理注册操作。如果一切正常,onSuccess钩子调用导航到主页;否则,onError钩子会显示一个有错误的吐司。
在代码中,有一个 TODO 指示缺少的东西;我们将在这篇文章的后面回到这一行。
登入
如果要构建身份验证流程,构建的第二步是登录。在这种情况下,SignIn 与 SignUp 非常相似;唯一改变的是端点和钩子的范围。
所以代码可以是这样的
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
}
我不想花太多时间描述此挂钩,因为它与 SignUp 非常相似,但仅包含对 SignIn 的引用。同样在这种情况下,我们将在以后的帖子中删除一个 TODO。
用户
身份验证流程的核心部分是将用户保存在状态中。为此,在这种情况下,最好的方法是创建一个名为useUser用户数据所有者的新挂钩。
钩子useUser必须有用户的数据,并且它必须将用户的数据保存在本地存储中,并在用户刷新页面或将来返回时检索它们。
让我们从处理本地存储的代码开始。通常,此代码是使用具有特定目标的小函数创建的,例如下一个。
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挂钩了。
让我们从代码开始
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,则返回 null 否则,它会调用 HTTP 端点。
该useQuery钩子与之前看到的其他钩子类似,但有两个新配置需要了解。
refetchOnMount:此选项对于防止挂钩在每次使用时重新加载数据很重要
initialData:此选项用于从本地存储加载数据;initialData 接受返回初始值的函数;如果定义了初始值,则 React 查询使用该值来刷新数据。
现在您已经拥有了身份验证流程的所有块,但现在是链接useSignUp和useSignIn钩子的时候了useUser。
使用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因为设置查询数据的key和useUser.
然后,用一个useEffectin useUserhook,当用户改变时,你可以移除或设置本地存储中的用户数据
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;它的实现很简单,可以通过这种方式完成
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
}
正如您所注意到的,挂钩返回一个简单的函数,该函数清除用户状态中的值并导航到页面sign-in。
好的,完美。现在您已经了解了使用 React Query 构建身份验证流程的所有概念.
如果你喜欢我的文章,记得关注获取更多的信息。感谢您的阅读,祝您有美好的一天!