【实战】 JWT、用户认证与异步请求(下) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(五)

news/2024/11/20 9:12:29/

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
    • 三、TS 应用:JS神助攻 - 强类型
    • 四、JWT、用户认证与异步请求
      • 1~5
      • 6.用useAuth切换登录与非登录状态
      • 7.用fetch抽象通用HTTP请求方法,增强通用性
      • 8.用useHttp管理JWT和登录状态,保持登录状态
      • 9.TS的联合类型、Partial和Omit介绍
      • 10.TS 的 Utility Types-Pick、Exclude、Partial 和 Omit 实现


学习内容来源:React + React Hook + TS 最佳实践-慕课网


相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:

版本
react & react-dom^18.2.0
react-router & react-router-dom^6.11.2
antd^4.24.8
@commitlint/cli & @commitlint/config-conventional^17.4.4
eslint-config-prettier^8.6.0
husky^8.0.3
lint-staged^13.1.2
prettier2.8.4
json-server0.17.2
craco-less^2.0.0
@craco/craco^7.1.0
qs^6.11.0
dayjs^1.11.7
react-helmet^6.1.0
@types/react-helmet^6.1.6
react-query^6.1.0
@welldone-software/why-did-you-render^7.0.1
@emotion/react & @emotion/styled^11.10.6

具体配置、操作和内容会有差异,“坑”也会有所不同。。。


一、项目起航:项目初始化与配置

  • 【实战】 项目起航:项目初始化与配置 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(一)

二、React 与 Hook 应用:实现项目列表

  • 【实战】 React 与 Hook 应用:实现项目列表 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二)

三、TS 应用:JS神助攻 - 强类型

  • 【实战】 TS 应用:JS神助攻 - 强类型 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(三)

四、JWT、用户认证与异步请求

1~5

  • 【实战】 JWT、用户认证与异步请求(上) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(四)

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 接口 401A 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 = nullconst token = auth.getToken()if (token) {// 由于要自定义 token ,这里使用 http 而非 useHttpconst 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 } }

10.TS 的 Utility Types-Pick、Exclude、Partial 和 Omit 实现

  • Pick:经过 泛型约束 生成一个新类型(理解为子类型?)
/*** From T, pick a set of properties whose keys are in the union K*/
type Pick<T, K extends keyof T> = {[P in K]: T[P];
};
  • Exclude: 如果 TU 的子类型则返回 never 不是则返回 T
/*** Exclude from T those types that are assignable to U*/
type Exclude<T, U> = T extends U ? never : T;

keyof:索引类型查询操作符(对于任何类型 Tkeyof T的结果为 T 上已知的公共属性名的联合。)

let man: keyof Person
// 相当于 let man: 'name' | 'age' | 'job'
// keyof Man === 'name' | 'age' | 'job' // true ???

T[K]:索引访问操作符(需要确保类型变量 K extends keyof T

in:遍历

extends:泛型约束

TS 在一定程度上可以理解为:类型约束系统


部分引用笔记还在草稿阶段,敬请期待。。。


http://www.ppmy.cn/news/615687.html

相关文章

EX Sports中文Telegram社区正式成立啦 欢迎中国地区的伙伴加入

完成任务即可领取EXS 任务&#xff1a; 1、 关注twitter3分 2、 转发本条推文2分 3、 加入中文Telegram社区5分 4、 将表单分享并且成功邀请好友5分/人 根据得分发放奖励 10分获得价值1USDT的EXS 20分获得价值2USDT的EXS 50分获得价值5UTSD的EXS 积分排名第一获得500…

谷歌该瑟瑟发抖了,在中国的鸿蒙取得成功后,欧洲也将挑战安卓

近日德国的手机制造商Hallo Welt Systeme UG发布一款Volla Phone 22手机&#xff0c;这款手机并未采用安卓系统&#xff0c;而是可以同时运行WindowsPhone 7、Linux、Firefox等操作系统&#xff0c;它希望为欧洲消费者提供更多样的选择&#xff0c;原因是谷歌安卓的垄断阻碍了欧…

新华LINUX牵手华旗资讯 强强联手打造新型移动存储设备

2005年12月12日&#xff0c;新华LINUX正式宣布&#xff0c;与北京华旗资讯数码科技有限公司签署共同合作协议&#xff0c;双方将合作推广新一代内置独立操作系统的智能移动存储产品。 目前&#xff0c;大部分移动存储产品还依旧停留于扩大存储容量、提高传输速度、改良外观造型…

华旗起诉海尔商标侵权案的前因后果

偶尔和朋友谈起华旗起诉海尔商标侵权案&#xff0c;虽然离这个案件正式起诉时间已经过去接近3个月&#xff0c;还是想在这里把老杳了解的情况向大家做个介绍&#xff0c;毕竟这起诉讼目前并没有结案&#xff0c;多了解些其中内情&#xff0c;或许对大家以后有所帮助。 事情的起…

曝荣耀仍未获得谷歌Android授权;诋毁鸿蒙的OPPO员工离职;微信圈子将停止运营|极客头条...

「极客头条」—— 技术人员的新闻圈&#xff01; CSDN 的读者朋友们早上好哇&#xff0c;「极客头条」来啦&#xff0c;快来看今天都有哪些值得我们技术人关注的重要新闻吧。 整理 | 梦依丹 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 一分钟速览新闻点&#…

谷歌开源PaLM API /Meta再裁员10000人/ 全国高中C9联盟成立…今日更多新鲜事在此...

日报君 发自 凹非寺量子位 | 公众号 QbitAI 今天是3月15日星期三&#xff0c;国际消费者权益日。 不只今天&#xff0c;每天都要很好的维权哦&#xff01; 最近科技圈都发生了哪些新鲜事&#xff1f;一起来和日报君看看~ 谷歌在Gmail、Docs等办公应用中引入AI生成功能 GPT-4发布…

7月31日科技资讯|华为否认鸿蒙为噱头;谷歌公布 6 大 iOS 漏洞;GitLab 又发安全补丁

「CSDN 极客头条」&#xff0c;是从 CSDN 网站延伸至官方微信公众号的特别栏目&#xff0c;专注于一天业界事报道。风里雨里&#xff0c;我们将每天为朋友们&#xff0c;播报最新鲜有料的新闻资讯&#xff0c;让所有技术人&#xff0c;时刻紧跟业界潮流。 整理 | 胡巍巍 责编…

扎克伯格15年邮件曝光:AR/VR平台全盘细节,谈收购Unity的优势

近期一份2015年时扎克伯格的一封邮件曝光&#xff0c;文中解释了Facebook在VR领域的战略策略和VR对未来的影响等&#xff0c;揭露了但是Facebook在VR/AR方向上的全盘计划&#xff0c;同时曝光当时Facebook就有收购Unity的计划&#xff0c;值得一看。 以下是摘要&#xff1a; 1&…