【实战】 九、深入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.配置redux-toolkit
      • 10.应用 redux-toolkit 管理模态框


学习内容来源: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.配置redux-toolkit

  • Redux Toolkit

redux-toolkit 是对 redux 的二次封装,主要解决三大痛点:

  • 配置复杂
  • 需要增加的包太多
  • 需要太多模板代码

由于项目最终不会使用到 redux,因此接下来新开一个分支用作学习开发,创建分支 redux-toolkit

安装依赖:

npm i react-redux @reduxjs/toolkit # --force

新建 src\store\index.tsx

import { configureStore } from "@reduxjs/toolkit"export const rootReducer = {}export const store = configureStore({reducer: rootReducer
})export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof store.getState>

新建 src\screens\ProjectList\projectList.slice.ts

import { createSlice } from "@reduxjs/toolkit";interface State {projectModalOpen: boolean;
}const initialState: State = {projectModalOpen: false
}export const projectListSlice = createSlice({name: 'projectListSlice',initialState,reducers: {openProjectModal(state, action) {},closeProjectModal(state, action) {}}
})

问:为什么这里可以直接给 state 的属性赋值?
答:redux 借助内置的 immer 来处理使其变为不可变数据的同时,创建“影子状态”最终整体替换原状态

10.应用 redux-toolkit 管理模态框

完善 src\screens\ProjectList\projectList.slice.ts

import { createSlice } from "@reduxjs/toolkit";
import { RootState } from "store";interface State {projectModalOpen: boolean;
}const initialState: State = {projectModalOpen: false,
};export const projectListSlice = createSlice({name: "projectListSlice",initialState,reducers: {openProjectModal(state) {state.projectModalOpen = true},closeProjectModal(state) {state.projectModalOpen = false},},
});export const projectListActions = projectListSlice.actions;export const selectProjectModalOpen = (state: RootState) => state.projectList.projectModalOpen

后续使用方式:

  • 引入
    • import { useDispatch, useSelector } from "react-redux";
    • import { projectListActions, selectProjectModalOpen } from "../projectList.slice";
  • 使用 hook 拿到 dispatchconst dispatch = useDispatch()
  • 使用 dispatch 调用打开模态框:() => dispatch(projectListActions.openProjectModal())
  • 使用 dispatch 调用关闭模态框:() => dispatch(projectListActions.closeProjectModal())
  • 使用 hook 获取模态框当前开闭状态:useSelector(selectProjectModalOpen)
    • useSelector 用来读根状态树

修改 src\store\index.tsx(引入 projectListSlice):

import { configureStore } from "@reduxjs/toolkit";
import { projectListSlice } from "screens/ProjectList/projectList.slice";export const rootReducer = {projectList: projectListSlice.reducer,
};export const store = configureStore({reducer: rootReducer,
});export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
  • ReturnType 用来读取函数返回值的类型

之前在 AuthenticatedApp(子封装模态框组件引入的地方) 创建状态量(const [isOpen, setIsOpen] = useState(false))分别一层一层传入使用到模态框的地方

现在不用啦,接下来开始使用 reduxdispatch 更改模态框状态:

编辑 src\authenticated-app.tsx

...
export const AuthenticatedApp = () => {
-  const [isOpen, setIsOpen] = useState(false);...return (<Container>
-      <PageHeader
-        projectButton={
-          <ButtonNoPadding type="link" onClick={() => setIsOpen(true)}>
-            创建项目
-          </ButtonNoPadding>
-        }
-      />
+      <PageHeader/><Main><Router><Routes><Routepath="/projects"element={
-                <ProjectList
-                  projectButton={
-                    <ButtonNoPadding
-                      type="link"
-                      onClick={() => setIsOpen(true)}
-                    >
-                      创建项目
-                    </ButtonNoPadding>
-                  }
-                />
+                <ProjectList/>}/>...</Routes></Router></Main>
-      <ProjectModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
+      <ProjectModal/></Container>);
};
- const PageHeader = (props: { projectButton: JSX.Element }) => {
+ const PageHeader = () => {...return (<Header between={true}><HeaderLeft gap={true}>...
-        <ProjectPopover {...props} />
+        <ProjectPopover/><span>用户</span></HeaderLeft>...</Header>);
};
...

编辑 src\screens\ProjectList\index.tsx

+ import { useDispatch } from "react-redux";
+ import { ButtonNoPadding } from "components/lib";
+ import { projectListActions } from "./projectList.slice";- export const ProjectList = ({
-   projectButton,
- }: {
-   projectButton: JSX.Element;
- }) => {
+ export const ProjectList = () => {...
+   const dispatch = useDispatch()return (<Container><Row justify="space-between"><h1>项目列表</h1>
-        {projectButton}
+        <ButtonNoPadding type="link" onClick={() => dispatch(projectListActions.openProjectModal())}>
+          创建项目
+        </ButtonNoPadding></Row>...<List
-        projectButton={projectButton}.../></Container>);
};
...

编辑 src\screens\ProjectList\components\List.tsx

...
+ import { useDispatch } from "react-redux";
+ import { projectListActions } from "../projectList.slice";interface ListProps extends TableProps<Project> {users: User[];refresh?: () => void;
-  projectButton: JSX.Element;
}// type PropsType = Omit<ListProps, 'users'>
export const List = ({ users, ...props }: ListProps) => {
+  const dispatch = useDispatch()...return (<Tablepagination={false}columns={[...{render: (text, project) => {
+            const items: MenuProps["items"] = [
+              {
+                key: "edit",
+                label: "编辑",
+                onClick: () => dispatch(projectListActions.openProjectModal()),
+              },
+            ];return (
-              <Dropdown dropdownRender={() => props.projectButton}>
+              <Dropdown menu={{items}}>...</Dropdown>);},},]}{...props}></Table>);
};

编辑 src\screens\ProjectList\components\ProjectModal.tsx

...
+ import { useDispatch, useSelector } from "react-redux";
+ import { projectListActions, selectProjectModalOpen } from "../projectList.slice";- export const ProjectModal = ({
-   isOpen,
-   onClose,
- }: {
-   isOpen: boolean;
-   onClose: () => void;
- }) => {
+ export const ProjectModal = () => {
+   const dispatch = useDispatch()
+   const projectModalOpen = useSelector(selectProjectModalOpen)return (
-     <Drawer onClose={onClose} open={isOpen} width="100%">
+     <Drawer
+       onClose={() => dispatch(projectListActions.closeProjectModal())}
+       open={projectModalOpen}
+       width="100%"
+     ><h1>Project Modal</h1>
-       <Button onClick={onClose}>关闭</Button>
+       <Button onClick={() => dispatch(projectListActions.closeProjectModal())}>关闭</Button></Drawer>);
};

编辑 src\screens\ProjectList\components\ProjectPopover.tsx

...
+ import { useDispatch } from "react-redux";
+ import { projectListActions } from "../projectList.slice";
+ import { ButtonNoPadding } from "components/lib";- export const ProjectPopover = ({
-   projectButton,
- }: {
-   projectButton: JSX.Element;
- }) => {
+ export const ProjectPopover = () => {
+   const dispatch = useDispatch()...const content = (<ContentContainer><Typography.Text type="secondary">收藏项目</Typography.Text><List>{starProjects?.map((project) => (
-           <List.Item>
+           <List.Item key={project.id}><List.Item.Meta title={project.name} /></List.Item>))}</List><Divider />
-      {projectButton}
+      <ButtonNoPadding type="link" onClick={() => dispatch(projectListActions.openProjectModal())}>
+        创建项目
+      </ButtonNoPadding></ContentContainer>);...
};
...

现在访问页面会发现有报错:

could not find react-redux context value; please ensure the component is wrapped in a <Provider>

这是因为没有将 reduxstore 绑定到全局 context

编辑 src\context\index.tsx

...
+ import { store } from "store";
+ import { Provider } from "react-redux";export const AppProvider = ({ children }: { children: ReactNode }) => {return (
+   <Provider store={store}><QueryClientProvider client={new QueryClient()}><AuthProvider>{children}</AuthProvider></QueryClientProvider>
+   </Provider>);
};

再次访问页面,功能 OK 了


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/24246.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

代码随想录算法训练营第五十六天| 583. 两个字符串的删除操作 72. 编辑距离

代码随想录算法训练营第五十六天| 583. 两个字符串的删除操作 72. 编辑距离 一、力扣583. 两个字符串的删除操作 题目链接 思路&#xff1a;相等时不删除&#xff0c;不相等时&#xff0c;两个字符串各删除一个&#xff0c;比大小&#xff0c;删除用步骤少的。 class Soluti…

CBCGPRibbon 添加背景图片

resource.h中声明资源的ID&#xff1a;ID_RIBBON_BACKIMAGE rc文件中添加png图片路径&#xff1a; ID_RIBBON_BACKIMAGE PNG DISCARDABLE "res\\bkribbon.png" 代码中添加下测&#xff1a; //添加背景图片 m_wndRibbonBar.SetBackgroundImage(ID_RIB…

机器学习:马尔可夫模型

后续遇到合适的案例会再补充 1 马尔可夫模型 马尔可夫模型(Markov Model, MM)是一种统计模型&#xff0c;广泛应用在自然语言处理等领域中。 1.1 数学定义 考虑一组随机变量序列 X { X 0 , X 1 , … , X t , … } X\{X_{0},X_{1},\dots,X_{t},\dots\} X{X0​,X1​,…,Xt​,……

C语言单链表OJ题(较易)

一、移除链表元素 leetcode链接 题目描述&#xff1a; 给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 思路&#xff1a; 正常遍历&#xff0c;找到value的值与题目中相同的结点去fr…

第5集丨Vue 江湖 —— 监视属性/侦听属性

目录 一、基本使用1.1 watch配置监视1.2 vm.$watch动态监视1.3 深度监视(deep watch)1.4 简写形式 二、computed和watch的对比2.1 使用watch实现setTimeout操作2.2 用computed无法实现setTimeout 三、其他注意事项3.1 vue devtools的bug3.2 xxxyyy格式3.3 将window传入data中 V…

Java后台生成微信小程序码并以流的形式返回给前端

后端代码 获取access_token import net.sf.json.JSONObject;public class WeChatUtil {/*** 获取token*/private static String ACCESSTOKENURL "https://api.weixin.qq.com/cgi-bin/token?grant_typeclient_credential&appid{appId}&secret{appSecret}"…

机器人开发--富锐雷达介绍

机器人开发--富锐雷达介绍 1 介绍参考 1 介绍 山东富锐光学科技有限公司是一家专注智能感知领域的激光雷达公司&#xff0c;致力于激光雷达前沿技术的开发和应用。 公司已累计完成数亿元融资&#xff0c;依托潍坊光电产业发展基础&#xff0c;自建生产线&#xff0c;达到年产…

LNMP安装

目录 1、LNMP简述&#xff1a; 1.1、概述 1.2、LNMP是一个缩写词&#xff0c;及每个字母的含义 1.3、编译安装与yum安装差异 1.4、编译安装的优点 2、通过LNMP创建论坛 2.1、 安装nginx服务 2.1.1、关闭防火墙 2.1.2、创建运行用户 2.1.3、 编译安装 2.1.4、 优化路…

Portraiture 4.0.3 for windows/Mac简体中文版(ps人像磨皮滤镜插件)

Imagenomic Portraiture系列插件作为PS磨皮美白必备插件&#xff0c;可以说是最强&#xff0c;今天它更新到了4.0.3版本。但是全网都没有汉化包&#xff0c;经过几个日夜汉化&#xff0c;终于汉化完成可能是全网首个Portraiture 4的汉化包&#xff0c;请大家体验&#xff0c;有…

2.12 Android ebpf帮助函数解读(十一)

201.long bpf_ringbuf_reserve_dynptr(void *ringbuf, u32 size, u64 flags, struct bpf_dynptr *ptr) 描述:通过 dynptr 接口在环形缓冲区 ringbuf 中保留有效负载size的字节。flags必须为 0。 即使保留失败,也需要调用ptr上的bpf_ringbuf_submit_dynptr或bpf_ringbuf_dis…

展示Streamlit文本魔力(六):从头顶到脚尖

文章目录 1 前言✨2 st.markdown - 引入丰富的Markdown文本3 st.title - 引入引人注目的大标题4 st.header - 引入简洁的小标题5 st.subheader - 添加次级标题6 st.caption - 添加解释性文字7 st.code - 显示代码块8 st.text - 显示文本9 st.latex - 显示LaTeX公式10 st.divide…

【JAVA】 javaSE中的数组|数组的概念使用

数组的概念 什么是Java中的数组 数组&#xff1a;可以看成是相同类型元素的一个集合。在内存中是一段连续的空间。在java中&#xff0c;包含6个整形类型元素的数组&#xff0c;可以看做是酒店中连续的6个房间. 1. 数组中存放的元素其类型相同 2. 数组的空间是连在一起的 3…

2023年第四届“华数杯”数学建模思路 - 案例:粒子群算法

# 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Particle Swarm Optimization,PSO&#xff09;是一种模仿鸟群、鱼群觅食行为发展起来的一种进化算…

Maven-搭建私有仓库

使用NEXUS REPOSITORY MANAGER 3在Windows上搭建私有仓库。 NEXUS REPOSITORY MANAGER 3 是一个仓库管理系统。 下载NEXUS3 官网上是无法下载的,所以网上搜nexus-3.18.1-01-win64就能搜到,下载即可。 安装NEXUS3 下载nexus-3.18.0-01-win64.zip至相应目录下(路径不要有中文)。 …

Go和Java实现建造者模式

Go和Java实现建造者模式 下面通过一个构造人身体不同部位的案例来说明构造者模式的使用。 1、建造者模式 建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式&#xff0c;它提供了 一种创建对象的最佳方式。 一个 Builder 类会…

【零基础学Rust | 基础系列 | 函数,语句和表达式】函数的定义,使用和特性

文章标题 简介一&#xff0c;函数1&#xff0c;函数的定义2&#xff0c;函数的调用3&#xff0c;函数的参数4&#xff0c;函数的返回值 二&#xff0c;语句和表达式1&#xff0c;语句2&#xff0c;表达式 总结&#xff1a; 简介 在Rust编程中&#xff0c;函数&#xff0c;语句…

JavaScript--Math(算数)对象

JavaScript的Math对象是一个内置对象&#xff0c;提供了用于执行数学任务的方法和属性。下面是一些常用的Math对象方法&#xff1a; 数值运算函数&#xff1a; abs(x)ceil(x)floor(x)max(x,y,z,...,n)min(x,y,z,...,n)pow(x,y)round(x)sqrt(x)trunc(x) 这些函数用于常见的数…

c++--二叉树应用

1.根据二叉树创建字符串 力扣 给你二叉树的根节点 root &#xff0c;请你采用前序遍历的方式&#xff0c;将二叉树转化为一个由括号和整数组成的字符串&#xff0c;返回构造出的字符串。 空节点使用一对空括号对 "()" 表示&#xff0c;转化后需要省略所有不影响字符…

hyperf 十二、自动化测试

文档教程&#xff1a;Hyperf 用co-phpunit提供测试&#xff0c;在composer中测试。 "scripts": {"test": "co-phpunit --prepend test/bootstrap.php -c phpunit.xml --colorsalways", } 测试中使用Hyperf\Testing\Client模拟请求,该类调用Hyp…

vscode中无法使用git解决方案

1 首先查看git安装目录 where git 2 找到bash.exe 的路径 比如&#xff1a;C:/Users/Wangzd/AppData/Local/Programs/Git/bin/bash 3 找到vscode的配置项setting.json 4 添加 "terminal.integrated.shell.windowns": "C:/Users/Wangzd/AppData/Local/Pr…