【实战】 四、JWT、用户认证与异步请求(下) —— React17

6.用useAuth切换登录与非登录状态

登录态 页面和 非登录态 页面分别整合(过程稀碎。。):

  • 新建文件夹及下面文件:unauthenticated-app
  • index.tsx
import { useState } from "react";
import { Login } from "./login";
import { Register } from "./register";

export const UnauthenticatedApp = () => {
  const [isRegister, setIsRegister] = useState(false);
  return (
    <div>
      {isRegister ? <Register /> : <Login />}
      <button onClick={() => setIsRegister(!isRegister)}>
        切换到{isRegister ? "登录" : "注册"}
      </button>
    </div>
  );
};

  • login.tsx(把 src\screens\login\index.tsx 剪切并更名)
import { useAuth } from "context/auth-context";
import { FormEvent } from "react";

export const Login = () => {
  const { login, user } = useAuth();
  // HTMLFormElement extends Element (子类型继承性兼容所有父类型)(鸭子类型:duck typing: 面向接口编程 而非 面向对象编程)
  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const username = (event.currentTarget.elements[0] as HTMLFormElement).value;
    const password = (event.currentTarget.elements[1] as HTMLFormElement).value;
    login({ username, password });
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="username">用户名</label>
        <input type="text" id="username" />
      </div>
      <div>
        <label htmlFor="password">密码</label>
        <input type="password" id="password" />
      </div>
      <button type="submit">登录</button>
    </form>
  );
};

  • register.tsx(把 src\screens\login\index.tsx 剪切并更名,代码中 login 相关改为 register
import { useAuth } from "context/auth-context";
import { FormEvent } from "react";

export const Register = () => {
  const { register, user } = useAuth();
  // HTMLFormElement extends Element (子类型继承性兼容所有父类型)(鸭子类型:duck typing: 面向接口编程 而非 面向对象编程)
  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const username = (event.currentTarget.elements[0] as HTMLFormElement).value;
    const password = (event.currentTarget.elements[1] as HTMLFormElement).value;
    register({ username, password });
  };
  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="username">用户名</label>
        <input type="text" id="username" />
      </div>
      <div>
        <label htmlFor="password">密码</label>
        <input type="password" id="password" />
      </div>
      <button type="submit">注册</button>
    </form>
  );
};

  • 删掉目录:src\screens\login
  • 新建文件:authenticated-app.tsx
import { useAuth } from "context/auth-context";
import { ProjectList } from "screens/ProjectList";

export const AuthenticatedApp = () => {
  const { logout } = useAuth();
  return (
    <div>
      <button onClick={logout}>登出</button>
      <ProjectList />
    </div>
  );
};

  • 修改 src\App.tsx(根据是否可以获取到 user 信息,决定展示 登录态 还是 非登录态 页面)
import { AuthenticatedApp } from "authenticated-app";
import { useAuth } from "context/auth-context";
import { UnauthenticatedApp } from "unauthenticated-app";
import "./App.css";

function App() {
  const { user } = useAuth();

  return (
    <div className="App">
      {user ? <AuthenticatedApp /> : <UnauthenticatedApp />}
    </div>
  );
}

export default App;

查看页面,尝试功能:

  • 切换登录/注册,正常
  • 登录:login 正常,但是 projectsusers 接口 401(A token must be provided)
  • F12 控制台查看 __auth_provider_token__ (Application - Storage - Local Storage - http://localhost:3000):

在这里插入图片描述

  • 注册:正常,默认直接登录(同登录,存储 user
7.用fetch抽象通用HTTP请求方法,增强通用性
  • 新建:src\utils\http.ts
import qs from "qs";
import \* as auth from 'auth-provider'

const apiUrl = process.env.REACT\_APP\_API\_URL;

interface HttpConfig extends RequestInit {
  data?: object,
  token?: string
}

export const http = async (funcPath: string, { data, token, headers, ...customConfig }: HttpConfig) => {
  const httpConfig = {
    method: 'GET',
    headers: {
      Authorization: token ? `Bearer ${token}` : '',
      'Content-Type': data ? 'application/json' : ''
    },
    ...customConfig
  }

  if (httpConfig.method.toUpperCase() === 'GET') {
    funcPath += `?${qs.stringify(data)}`
  } else {
    httpConfig.body = JSON.stringify(data || {})
  }

  // axios 和 fetch 不同,axios 会在 状态码 不为 2xx 时,自动抛出异常,fetch 需要 手动处理
  return window.fetch(`${apiUrl}/${funcPath}`, httpConfig).then(async res => {
    if (res.status === 401) {
      // 自动退出 并 重载页面
      await auth.logout()
      window.location.reload()
      return Promise.reject({message: '请重新登录!'})
    }
    const data = await res.json()
    if (res.ok) {
      return data
    } else {
      return Promise.reject(data)
    }
  })
}

  • 类型定义思路:按住 Ctrl ,点进 fetch,可见:fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;,因此第二个参数即为 RequestInit 类型,但由于有自定义入参,因此自定义个继承 RequestInit 的类型
  • customConfig 会覆盖前面已有属性
  • 需要手动区别 getpost 不同的携参方式
  • axiosfetch 不同,axios 会在 状态码 不为 2xx 时,自动抛出异常,fetch 需要 手动处理
  • 留心 Authorization (授权)不要写成 Authentication (认证),否则后面会报401,且很难找出问题所在
8.用useHttp管理JWT和登录状态,保持登录状态
  • 为了使请求接口时能够自动携带 token 定义 useHttp: src\utils\http.ts
...
export const http = async (
  funcPath: string,
  { data, token, headers, ...customConfig }: HttpConfig = {} // 参数有 默认值 会自动变为 可选参数
) => {...}
...
export const useHttp = () => {
  const { user } = useAuth()
  // TODO 学习 TS 操作符
  return (...[funcPath, customConfig]: Parameters<typeof http>) => http(funcPath, { ...customConfig, token: user?.token })
}

  • 函数定义时参数设定 默认值,该参数即为 可选参数
  • 参数可以解构赋值后使用 rest 操作符降维,实现多参
  • Parameters 操作符可以将函数入参类型复用
  • src\screens\ProjectList\index.tsx 中使用 useHttp(部分原有代码省略):
...
import { useHttp } from "utils/http";

export const ProjectList = () => {
  ...
  const client = useHttp()

  useEffect(() => {
    // React Hook "useHttp" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
    client('projects', { data: cleanObject(lastParam)}).then(setList)
    // React Hook useEffect has a missing dependency: 'client'. Either include it or remove the dependency array.
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [lastParam]); 

  useMount(() => client('users').then(setUsers));

  return (...);
};

  • useHttp 不能在 useEffectcallback 中直接使用,否则会报错:React Hook "useHttp" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.,建议如代码中所示使用(client 即 携带 tokenhttp 函数)
  • 依赖中只有 lastParam ,会警告:React Hook useEffect has a missing dependency: 'client'. Either include it or remove the dependency array.,但是添加 client 会无法通过相等检查并导致无限的重新渲染循环。(当前代码中最优解是添加 eslint 注释,其他可参考但不适用:https://www.cnblogs.com/chuckQu/p/16608977.html
  • 检验成果:登录即可见 projectsusers 接口 200,即正常携带 token,但是当前页面刷新就会退出登录(user 初始值为 null),接下来优化初始化 user(src\context\auth-context.tsx):
...
import { http } from "utils/http";
import { useMount } from "utils";

interface AuthForm {...}

const initUser = async () => {
  let user = null
  const token = auth.getToken()
  if (token) {
    // 由于要自定义 token ,这里使用 http 而非 useHttp
    const data = await http('me', { token })
    user = data.user
  }
  return user
}
...
export const AuthProvider = ({ children }: { children: ReactNode }) => {
  ...
  useMount(() => initUser().then(setUser))
  return (...);
};
...

思路分析:定义 initUser ,并在 AuthProvider 组件 挂载时调用,以确保只要在 localStorage 中存在 token(未登出或清除),即可获取并通过预设接口 me 拿到 user ,完成初始化

至此为止,注册登录系统(功能)闭环

9.TS的联合类型、Partial和Omit介绍

联合类型

type1 | type2

交叉类型

type1 & type2

类型别名

type typeName = typeValue

类型别名在很多情况下可以和 interface 互换,但是两种情况例外:

  • typeValue 涉及交叉/联合类型
  • typeValue 涉及 Utility Types (工具类型)

TS 中的 typeof 用来操作类型,在静态代码中使用(JStypeof 在代码运行时(runtime)起作用),最终编译成的 JS 代码不会包含 typeof 字样

Utility Types(工具类型) 的用法:用泛型的形式传入一个类型(typeNametypeof functionName)然后进行类型操作

常用 Utility Types

  • Partial:将每个子类型转换为可选类型
/\*\*
 \* Make all properties in T optional
 \*/
type Partial<T> = {
    [P in keyof T]?: T[P];
};

  • Omit:删除父类型中的指定子类型并返回新类型
/\*\*
 \* Construct a type with the properties of T except for those in type K.
 \*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

案例:

type Person = {
  name: string,
  age: number,
  job: {
    salary: number
  }
}

const CustomPerson: Partial<Person> = {}
const OnlyJobPerson: Omit<Person, 'name' | 'age'> = { job: { salary: 3000 } }



**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Linux运维工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Linux运维全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/3f9355c9a526b713d93f661dcfabbd51.png)
![img](https://img-blog.csdnimg.cn/img_convert/19cd8c8d7ca71e4da4857118da8c2d51.png)
![img](https://img-blog.csdnimg.cn/img_convert/44c05345d90af9377e3709c28eed657b.png)
![img](https://img-blog.csdnimg.cn/img_convert/8fc581d4bf5478a4592ebd95ece10c0d.png)
![img](https://img-blog.csdnimg.cn/img_convert/e2c3263673254f411072163b4797c7bb.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Linux运维知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加VX:vip1024b (备注Linux运维获取)**
![img](https://img-blog.csdnimg.cn/img_convert/2642f7a2595d067b435f06f028709579.jpeg)

![](https://img-blog.csdnimg.cn/img_convert/9a8cb5f8c0ec69e6499adead0da6e95b.png)



最全的Linux教程,Linux从入门到精通

======================

1.  **linux从入门到精通(第2版)**

2.  **Linux系统移植**

3.  **Linux驱动开发入门与实战**

4.  **LINUX 系统移植 第2版**

5.  **Linux开源网络全栈详解 从DPDK到OpenFlow**



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/59742364bb1338737fe2d315a9e2ec54.png)



第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
![img](https://img-blog.csdnimg.cn/img_convert/2ff13e5c620128754a18c829f37cb433.jpeg)

盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。



![华为18级工程师呕心沥血撰写3000页Linux学习笔记教程](https://img-blog.csdnimg.cn/img_convert/9d4aefb6a92edea27b825e59aa1f2c54.png)



**本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。**

> 需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论




**一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
[外链图片转存中...(img-Gq2mt7ep-1712878591698)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值