当前位置: 首页 > news >正文

【实战】 九、深入React 状态管理与Redux机制(五) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十)

文章目录

    • 一、项目起航:项目初始化与配置
    • 二、React 与 Hook 应用:实现项目列表
    • 三、TS 应用:JS神助攻 - 强类型
    • 四、JWT、用户认证与异步请求
    • 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式
    • 六、用户体验优化 - 加载中和错误状态处理
    • 七、Hook,路由,与 URL 状态管理
    • 八、用户选择器与项目编辑功能
    • 九、深入React 状态管理与Redux机制
      • 1&2
      • 3&4
      • 5~8
      • 9&10
      • 11.用redux-thunk管理登录状态


学习内容来源: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

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


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

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

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

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

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

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

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

  • 四、 JWT、用户认证与异步请求(上)

  • 四、 JWT、用户认证与异步请求(下)

五、CSS 其实很简单 - 用 CSS-in-JS 添加样式

  • 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(上)

  • 五、CSS 其实很简单 - 用 CSS-in-JS 添加样式(下)

六、用户体验优化 - 加载中和错误状态处理

  • 六、用户体验优化 - 加载中和错误状态处理(上)

  • 六、用户体验优化 - 加载中和错误状态处理(中)

  • 六、用户体验优化 - 加载中和错误状态处理(下)

七、Hook,路由,与 URL 状态管理

  • 七、Hook,路由,与 URL 状态管理(上)

  • 七、Hook,路由,与 URL 状态管理(中)

  • 七、Hook,路由,与 URL 状态管理(下)

八、用户选择器与项目编辑功能

  • 八、用户选择器与项目编辑功能(上)

  • 八、用户选择器与项目编辑功能(下)

九、深入React 状态管理与Redux机制

1&2

  • 九、深入React 状态管理与Redux机制(一)

3&4

  • 九、深入React 状态管理与Redux机制(二)

5~8

  • 九、深入React 状态管理与Redux机制(三)

9&10

  • 九、深入React 状态管理与Redux机制(四)

11.用redux-thunk管理登录状态

既然模态框使用 redux 来管理了,而 reduxcontext 是竞争关系,那可以尝试将之前管理登录状态的 context 改为 redux

新建 src\store\auth.slice.ts

import { User } from "screens/ProjectList/components/SearchPanel";
import { createSlice } from "@reduxjs/toolkit";
import * as auth from 'auth-provider'
import { AuthForm, initUser as _initUser } from "context/auth-context";
import { AppDispatch, RootState } from "store";interface State {user: User | null
}const initialState: State = {user: null
}export const authSlice = createSlice({name: 'auth',initialState,reducers: {setUser(state, action) {state.user = action.payload}}
})const { setUser } = authSlice.actionsexport const selectUser = (state: RootState) => state.auth.userexport const login = (form: AuthForm) => (dispatch: AppDispatch) => auth.login(form).then(user => dispatch(setUser(user)))
export const register = (form: AuthForm) => (dispatch: AppDispatch) => auth.register(form).then(user => dispatch(setUser(user)))
export const logout = () => (dispatch: AppDispatch) => auth.logout().then(() => dispatch(setUser(null)))
export const initUser = () => (dispatch: AppDispatch) => _initUser().then(user => dispatch(setUser(user)))

需要提前将 src\context\auth-context.tsx 中的 interface AuthForminitUser(视频中对应bootstrapUser) 导出

最后在 src\store\index.ts 中统一注册:

...
import { authSlice } from "./auth.slice";// 集中状态注册
export const rootReducer = {projectList: projectListSlice.reducer,auth: authSlice.reducer
};
...

接下来使用 redux 中的 auth 相关功能替换原有 context 提供的 auth 的功能

重构需要找代码上游中集中的一个点:useAuth

修改 src\context\auth-context.tsx

+ import { ReactNode, useCallback } from "react";
...
+ import * as authStore from 'store/auth.slice'
+ import { useDispatch, useSelector } from "react-redux";
+ import { AppDispatch } from "store";- interface AuthForm {
+ export interface AuthForm {username: string;password: string;
}- const initUser = async () => {
+ export const initUser = async () => {let user = null;const token = auth.getToken();if (token) {// 由于要自定义 token ,这里使用 http 而非 useHttpconst data = await http("me", { token });user = data.user;}return user
};- const AuthContext = React.createContext<
-   | {
-       user: User | null;
-       login: (form: AuthForm) => Promise<void>;
-       register: (form: AuthForm) => Promise<void>;
-       logout: () => Promise<void>;
-     }
-   | undefined
- >(undefined);- AuthContext.displayName = "AuthContext";export const AuthProvider = ({ children }: { children: ReactNode }) => {// 这里要考虑到初始值的类型与后续值类型,取并组成一个泛型const {
-     data: user,error,isLoading,isReady,isError,run,
-     setData: setUser,} = useAsync<User | null>();
+   // const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
+   // const dispatch: AppDispatch = useDispatch()
+ 
+   // 这种写法虽然消除了代码中的报错,但是控制台会有报错
+   // useMount(async () => run(dispatch(await initUser())));
+   // useMount(() => run(dispatch(initUser())));
+   // 还原后登录保持功能已经是不好使的了useMount(() => run(initUser()));-   const login = (form: AuthForm) => auth.login(form).then(setUser);
-   const register = (form: AuthForm) => auth.register(form).then(setUser);
-   const logout = () => auth.logout().then(() => setUser(null));if (isReady || isLoading) {return <FullPageLoading />;}if (isError) {return <FullPageErrorFallback error={error} />;}-   return (
-     <AuthContext.Provider
-       children={children}
-       value={{ user, login, register, logout }}
-     />
-   );
+   return <div>
+     { children }
+   </div>
};export const useAuth = () => {
-   const context = React.useContext(AuthContext);
-   if (!context) {
-     throw new Error("useAuth 必须在 AuthProvider 中使用");
-   }
-   return context;
+   // 这种写法有报错
+   // const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()
+   const dispatch: AppDispatch = useDispatch()
+   const user = useSelector(authStore.selectUser)
+   const login = useCallback((form: AuthForm) => dispatch(authStore.login(form)), [dispatch])
+   const register = useCallback((form: AuthForm) => dispatch(authStore.register(form)), [dispatch])
+   const logout = useCallback(() => dispatch(authStore.logout()), [dispatch])
+   // const login = useCallback((form: AuthForm) => authStore.login(form), [])
+   // const register = useCallback((form: AuthForm) => authStore.register(form), [])
+   // const logout = useCallback(() => authStore.logout(), [])
+   // login({ username: '123', password: '123' }).then()
+   return { user, login, register, logout };
};

这种写法会报错 const dispatch: (...args: unknown[]) => Promise<User> = useDispatch()

不能将类型“Dispatch<AnyAction>”分配给类型“(...args: unknown[]) => Promise<User>”。参数“action”和“args” 的类型不兼容。不能将类型“unknown”分配给类型“AnyAction”

替换为 const dispatch: AppDispatch = useDispatch() 后正常

useMount(async () => run(dispatch(await initUser()))) 中不加 async..await 的话会有如下报错提示:

没有与此调用匹配的重载。第 1 个重载(共 3 个),“(thunkAction: ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>): Promise<User | null>”,出现以下错误。类型“Promise<any>”的参数不能赋给类型“ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>”的参数。类型“Promise<any>”提供的内容与签名“(dispatch: ThunkDispatch<{ projectList: State; auth: State; }, undefined, AnyAction>, getState: () => { projectList: State; auth: State; }, extraArgument: undefined): Promise<...>”不匹配。第 2 个重载(共 3 个),“(action: AnyAction): AnyAction”,出现以下错误。类型“Promise<any>”的参数不能赋给类型“AnyAction”的参数。类型 "Promise<any>" 中缺少属性 "type",但类型 "AnyAction" 中需要该属性。第 3 个重载(共 3 个),“(action: AnyAction | ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>): AnyAction | Promise<...>”,出现以下错误。类型“Promise<any>”的参数不能赋给类型“AnyAction | ThunkAction<Promise<User | null>, { projectList: State; auth: State; }, undefined, AnyAction>”的参数。不能将类型“Promise<any>”分配给类型“AnyAction”。ts(2769)
auth-context.tsx(41, 31): 是否忘记使用 "await"?
index.d.ts(19, 3): 在此处声明了 "type"。
auth-context.tsx(41, 31): 是否忘记使用 "await"?
auth-context.tsx(41, 31): 是否忘记使用 "await"?

最终运行结果:登录,注册,登出功能都正常,只有登录保持不正常


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

相关文章:

【实战】 九、深入React 状态管理与Redux机制(五) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二十)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表三、TS 应用&#xff1a;JS神助攻 - 强类型四、JWT、用户认证与异步请求五、CSS 其实很简单 - 用 CSS-in-JS 添加样式六、用户体验优化 - 加载中和错误状态处理七、Hook&…...

PHP傻瓜也能搭建自己框架

PHP最简单自定义自己的框架&#xff08;一&#xff09; PHP最简单自定义自己的框架创建目录结构&#xff08;二&#xff09; PHP最简单自定义自己的框架定义常量自动生成目录&#xff08;三&#xff09; PHP最简单自定义自己的框架控制器自动加载运行&#xff08;四&#xf…...

为什么商业基础软件需要开源

Bytebase 本身是一家商业软件公司&#xff0c;而作为最核心资产的代码从 Day 0 却是开源的。同时我们还是 star-history.com 的运营者&#xff0c;大家在各种开源渠道会看到它生成的图&#xff1a; 一直以来&#xff0c;常会被别人问起的一个问题&#xff0c;就是为什么 Byteba…...

【自用】云服务器 使用 docker 搭建 HomeAssistant + MQTT 物联网平台

总览 1.搭建流程概述 2.准备工作 3.开始搭建&#xff01; 4.总结 如果想看 ESP32 或其他使用 MicroPython 编程的单片机如何连接到该云服务器&#xff0c;实现 HomeAssistant 控制 单片机的内容&#xff0c;请看我这篇博客的下一篇。 一、搭建流程概述 0.总体流程 我们需要…...

ABAP: SQL 多值查询

基础查数据 问题举例&#xff1a;例如查物料类型为ZFRT、ZROH和ZRSA的物料编码。 1、直接查询&#xff0c;三种不同类型的物料类型是或的关系。 SELECT DISTINCT ma~matnr ma~mtartFROM mara AS maINNER JOIN mbewh AS mbON ma~matnr mb~matnrINTO CORRESPONDING FIELDS OF…...

分布式学习最佳实践:从分布式系统的特征开始

正文   在延伸feature&#xff08;分布式系统需要考虑的特性&#xff09;的时候&#xff0c;我逐渐明白&#xff0c;这是因为要满足这些feature&#xff0c;才设计了很多协议与算法&#xff0c;也提出了一些理论。比如说&#xff0c;这是因为要解决去中心化副本的一致性问题&…...

第三章 图论 No.8最近公共祖先lca, tarjan与次小生成树

文章目录 lcaTarjan板子题&#xff1a;1172. 祖孙询问lca或tarjan&#xff1a;1171. 距离356. 次小生成树352. 闇の連鎖 lca O ( m l o g n ) O(mlogn) O(mlogn)&#xff0c;n为节点数量&#xff0c;m为询问次数&#xff0c;lca是一种在线处理询问的算法 自己也是自己的祖先 倍…...

[Kubernetes]Kubeflow Pipelines - 基本介绍与安装方法

1. 背景 近些年来&#xff0c;人工智能技术在自然语言处理、视觉图像和自动驾驶方面都取得不小的成就&#xff0c;无论是工业界还是学术界大家都在惊叹一个又一个的模型设计。但是对于真正做过算法工程落地的同学&#xff0c;在惊叹这些模型的同时&#xff0c;更多的是在忧虑如…...

Sui网络的稳定性和高性能

Sui的最初的协议开发者设计了可扩展的网络&#xff0c;通过水平扩展的方式来保持可负担得起的gas费用。其他区块链与之相比&#xff0c;则使用稀缺性和交易成本来控制网络活动。 Sui主网上线前90天的数据指标证明了这一设计概念&#xff0c;在保持100&#xff05;正常运行的同…...

RabbitMQ 安装教程

RabbitMQ 安装教程 特殊说明 因为RabbitMQ基于Erlang开发&#xff0c;所以安装时需要先安装Erlang RabbitMQ和Erlang版本对应关系 查看地址&#xff1a;www.rabbitmq.com/which-erlan… 环境选择 Erlang: 23.3及以上 RabbitMQ: 3.10.1Windows 安装 1. 安装Erlang 下载地…...

STM32F429IGT6使用CubeMX配置GPIO点亮LED灯

1、硬件电路 2、设置RCC&#xff0c;选择高速外部时钟HSE,时钟设置为180MHz 3、配置GPIO引脚 4、生成工程配置 5、部分代码 6、实验现象...

DOM的节点操作+事件高级+DOM事件流+事件对象

一.节点操作 1.父节点: node.parentNode 得到的是离元素最近的父级节点 2.子节点: parentNode.childNodes 所有的子节点 包含元素节点 文本节点等等parentNode.children (非标准) 获取所有的子元素节点,实际开发常用 parentNode.firstChild 获取…...

云端剪切板,让你的数据同步无界

云端剪切板&#xff0c;让你的数据同步无界&#xff01; 每个人都应该保护自己的数据&#xff0c;同时使它易于访问和共享。这就是我们的云剪切板网站诞生的原因&#xff01;无论你在哪里&#xff0c;只要登录我们的网站&#xff0c;就可以随时随地使用你的剪切板数据。 你可…...

Location匹配与Rewrite重写

一、常见的Nginx正则表达式 ^ &#xff1a;匹配输入字符串的起始位置 $ &#xff1a;匹配输入字符串的结束位置 * &#xff1a;匹配前面的字符零次或多次。如“ol*”能匹配“o”及“ol”、“oll”&#xff1a;匹配前面的字符一次或多次。如“ol”能匹配“ol”及“oll”、“oll…...

Docker源码阅读 - goland环境准备

docker 源码分为两部分 cli 和 moby&#xff08;docker&#xff09; tips: docker是从moby拷贝过去的&#xff1b;docker整体是一个C-S架构&#xff0c;cli客户端&#xff0c;docker服务端 docker-ce&#xff1a;https://github.com/docker/docker-ce cli&#xff1a;https://…...

数据库信息速递 -- MariaDB 裁员后,前景不确定 (翻译)

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加 liuaustin3微信号 &#xff0c;在新加的朋友会分到3群&#xff…...

4.1 Windows终端安全

数据参考&#xff1a;CISP官方 目录 安全安装保护账户安全本地安全策略安全中心系统服务安全其他安全设置软件安全获取 一、安全安装&#xff08;以安装windows系统为例&#xff09; 选择合适的版本 商业版本&#xff1a;家庭版、专业版、专业工作站版、企业版特殊版本&…...

win10强制卸载奇安信天擎

1、win r 打开运行 2、输入msconfig进入系统配置面板 3、点击引导&#xff0c;修改安全引导配置项 4、重启系统&#xff08;桌面会变成纯黑背景&#xff0c;符合预期&#xff0c;莫紧张&#xff09; 5、删除安装的文件夹 若是安装天擎时选择的自定义安装&#xff0c;则配置…...

npm常用命令

npm -v&#xff1a;查看 npm 版本 npm init&#xff1a;初始化后会出现一个 Package.json 配置文件&#xff0c;可以在后面加上 -y&#xff0c;快速跳到问答界面 npm install&#xff1a;会根据项目中的 package.json 文件自动给下载项目中所需的全部依赖 npm insall 包含 -…...

(一)创建型设计模式:4、原型模式(Prototype Pattern)

目录 1、原型模式的含义 2、C实现原型模式的简单实例 1、原型模式的含义 通过复制现有对象来创建新对象&#xff0c;而无需依赖于显式的构造函数或工厂方法&#xff0c;同时又能保证性能。 The prototype pattern is a creational design pattern in software development. …...

高压均质机的构造与工作原理解析

于乳业加工的生产车间里&#xff0c;有一台设备。在制药制备的生产车间里&#xff0c;同样有一台设备。在纳米材料的生产车间里&#xff0c;仍有一台设备。此设备在关键工序里&#xff0c;担当着决定性的角色。物料经由它处理后&#xff0c;粒径一下子迅速变细。物料经由它处理…...

保姆级教程:用GD32F103的DAC+TIMER+DMA生成正弦波,示波器实测波形稳如老狗

GD32F103实战&#xff1a;DACTIMERDMA正弦波生成全解析 最近在调试一个音频信号发生器项目时&#xff0c;发现不少初学者在使用GD32的DAC功能时都会遇到波形不稳定、配置复杂的问题。今天我就以GD32103C-START开发板为例&#xff0c;手把手带大家实现一个零CPU占用的正弦波发生…...

MCP 2026脱敏策略配置失效导致患者身份重识别?:从真实等保2.0通报案例反推6层校验配置法

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;MCP 2026脱敏策略配置失效导致患者身份重识别&#xff1f;&#xff1a;从真实等保2.0通报案例反推6层校验配置法 事件还原&#xff1a;某三甲医院MCP平台被通报的根源 2024年Q2&#xff0c;国家等保测…...

OpenSpeedy:开源游戏变速工具完整指南与高效配置方案

OpenSpeedy&#xff1a;开源游戏变速工具完整指南与高效配置方案 【免费下载链接】OpenSpeedy &#x1f3ae; An open-source game speed modifier. 项目地址: https://gitcode.com/gh_mirrors/op/OpenSpeedy 你是否曾经在玩单机游戏时&#xff0c;希望游戏节奏能够按照…...

CompressO视频压缩指南:3步将大文件缩小90%的终极解决方案

CompressO视频压缩指南&#xff1a;3步将大文件缩小90%的终极解决方案 【免费下载链接】compressO Convert any video/image into a tiny size. 100% free & open-source. Available for Mac, Windows & Linux. 项目地址: https://gitcode.com/gh_mirrors/co/compres…...

Ultralytics YOLOv11多光谱目标检测深度解析:三步实现高效跨通道视觉识别

Ultralytics YOLOv11多光谱目标检测深度解析&#xff1a;三步实现高效跨通道视觉识别 【免费下载链接】ultralytics Ultralytics YOLO &#x1f680; 项目地址: https://gitcode.com/GitHub_Trending/ul/ultralytics 在农业遥感监测、夜间安防监控、医疗影像分析等场景中…...

手把手教你搞定PC网站支付宝扫码登录:从申请到上线的完整避坑指南

支付宝扫码登录全流程实战&#xff1a;从开发配置到异常处理 在PC端网站集成支付宝扫码登录功能&#xff0c;已经成为许多企业提升用户体验的重要选择。相比传统的账号密码登录方式&#xff0c;扫码登录不仅减少了用户记忆成本&#xff0c;还大幅提升了登录流程的安全性和便捷性…...

苹果USB网络共享驱动终极安装指南:3分钟解决Windows连接问题

苹果USB网络共享驱动终极安装指南&#xff1a;3分钟解决Windows连接问题 【免费下载链接】Apple-Mobile-Drivers-Installer Powershell script to easily install Apple USB and Mobile Device Ethernet (USB Tethering) drivers on Windows! 项目地址: https://gitcode.com/…...

从ResNet到DenseNet:图解Element-wise Add和Concat如何塑造了现代CNN架构

从ResNet到DenseNet&#xff1a;图解Element-wise Add和Concat如何塑造了现代CNN架构 在深度学习的演进历程中&#xff0c;神经网络架构设计经历了从简单堆叠到精心设计的转变。2015年&#xff0c;ResNet通过残差连接&#xff08;Residual Connection&#xff09;彻底改变了卷积…...

GnuPG 2.5.19 版本发布:新增功能、修复漏洞,旧版 2 个月后停维!

什么是 GnuPGGNU 隐私卫士&#xff08;GnuPG&#xff0c;GPG&#xff09;是 OpenPGP 和 S/MIME 标准的完整且免费的实现。它能对数据和通信进行加密和签名&#xff0c;有多功能的密钥管理系统&#xff0c;还有访问公钥目录的模块。GnuPG 本身是命令行工具&#xff0c;方便和其他…...