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
正常,但是projects
和users
接口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
会覆盖前面已有属性- 需要手动区别
get
和post
不同的携参方式axios
和fetch
不同,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
不能在useEffect
的callback
中直接使用,否则会报错: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
即 携带token
的http
函数)- 依赖中只有
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)
- 检验成果:登录即可见
projects
和users
接口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
用来操作类型,在静态代码中使用(JS
的 typeof
在代码运行时(runtime
)起作用),最终编译成的 JS
代码不会包含 typeof
字样
Utility Types
(工具类型) 的用法:用泛型的形式传入一个类型(typeName
或 typeof 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)]