5. React 查询 - 身份验证流程

每个应用程序都应该处理身份验证流程;在本文中,您将了解如何使用 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 构建身份验证流程的所有概念.

如果你喜欢我的文章,记得关注获取更多的信息。感谢您的阅读,祝您有美好的一天!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Q shen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值