React 项目结构小结
React 项目结构小结
简单的记录一下目前 React 项目用的依赖和实现
摸索了大半年了大概构建一套用起来还算轻松的体系……?基本上应该是说可以应对大部分的项目了
使用的依赖
目前项目还在 refactoring 的阶段,所以乱得很,这里是新建一个空的项目作为案例,package.json 中新增添的依赖如下:
{"dependencies": {"@reduxjs/toolkit": "^1.9.7","@ui-framework/keycloak-auth": "^4.0.0","axios": "^1.6.0","dayjs": "^1.11.10","lodash": "^4.17.21","react-redux": "^8.1.3","react-router-dom": "^6.18.0","redux-logger": "^3.0.6","styled-components": "^6.1.0","uuid": "^9.0.1"}
}
版本不一定是最新的,这个取决于我们的 nexux 库更新的有多勤快
主要归类如下:
-
状态
-
@reduxjs/toolkit
这是主要的管理部分,RTK(Redux Toolkit) 是迭代后的版本,
-
react-redux
RTK 的依赖
-
redux-logger(可选)
用来查看 state 变化的插件
-
-
API 相关
- axios
-
路由
其实可以的话是想找一下依赖,看看有没有把路由归并到 redux 管理的实现,之前看到的一些实现要么是 v5 的,v6 不支持,要么就是 v6 可以支持,但是比较 buggy,现在暂时没什么时间做这个,晚点再折腾
- react-router-dom
-
验证
-
keycloak-auth
这是一个登录的依赖,token、profile 等会通过 keycloak-auth 返回
-
-
UI
公司内部其实有实现自己的 UI 库,所以我就放两个底层用的依赖。其他关于 radio 之类的,都是项目内部实现的
-
styled-components
CSS-in-JS
-
react-table
react-table 本身是一个 headless 的库,需要具体实现 UI
-
react-select
这个比较方便的一点在于可以使用 async dropdown,即通过点击
load more
或者scroll
事件可以触发 API 调用搭配好 pagination 的 query 可以比较好的提升用户体验
-
-
util 相关
-
dayjs
取代 momentjs
-
uuid
主要用在 API 调用方面,有的情况后端返回的 id 是 UUID
-
lodash
-
底层用的还是 create-react-app
,主要是因为脚手架一来确实方便,升级也可以直接通过升级 react-script
进行集成管理。二来,我们的项目需求并没有复杂到需要将 webpack 单独拆出来,做对应的优化等
基础结构
目前想要实现的结构如下:
-
components
这里放置了一些封装好的 UI,也就是我们根据自己内部的业务需求实现的一些 wrapper,主要的类型有:button, modal, layout, table 等
-
constants
这里是一些共用的逻辑,这个里面的分类是按照 model 进行的分类
我们的项目是比较强类型的,而且是 2b 业务,所以主要就是表单+表格的功能,而每个表单/表格有需要有独立的 structure 传到 UI 库中形成对应的结构,因此每个 model 对应的 structure 可以保存在 constant 中
另外一些可以保存的常量有 model 的类型,这一块目前放到
types
里,不过建于types
对 TS 来说是生成.d.ts
的文件的地方,所以这个迟早是要修改的 -
pages
每一个渲染的页面
-
store
redux store 的相关管理
-
types
目前用来放
model
的 type,不过按照上面说的,想要移到const
中 -
utils
一些相关的 helpers,包括环境、date、entity(model) 之类的
一些实现
其实主要还是 redux 相关的部分的修改,其他部分要么就是之前已经写过笔记了,要么就是跟具体业务相关的,这里不太好记
react router dom
v5 的使用:[React 基础系列] React Router 的基本应用 和 v6 的升级:React Router DOM 升级到 v6 后的一些报错信息
就目前来说我们还是待在 v5 没有动,不过之后我需要对已经实现的 Router 进行一个重构,到时候会实现一下升级 v6,不过按照计划来说,这也是明年二/三月份之后的事情……
现在到明年二/三月份主要还是需要将所有的 page 转成 redux-based
redux
Redux 的使用其实之前也有写过,大概如下:
- Redux Toolkit 调用 API 的四种方式
- []async thunk 解决 API 调用的依赖问题(https://goldenaarcher.blog.csdn.net/article/details/129002505)
- Redux 错误处理
这里不会太进入细节,简单的说一下每个 slice 里面的简单实现,以及 pages 中的 component 怎么调用的,基本结构大致如下:
index.ts
这里代码其实没什么好变的,基本上是一个万能模板了:
import {configureStore,EnhancedStore,ThunkDispatch,AnyAction,Store,
} from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import logger from 'redux-logger';
import reducers from './slices';// reference: https://stackoverflow.com/questions/70143816/argument-of-type-asyncthunkactionany-void-is-not-assignable-to-paramete// 1. Get the root state's type from reducers
export type RootState = ReturnType<typeof store.getState>;// 2. Create a type for thunk dispatch
export type AppThunkDispatch = ThunkDispatch<RootState, any, AnyAction>;// 3. Create a type for store using RootState and Thunk enabled dispatch
export type AppStore = Omit<Store<RootState, AnyAction>, 'dispatch'> & {dispatch: AppThunkDispatch;
};export const store: EnhancedStore = configureStore({reducer: reducers,middleware: (getDefaultMiddleware) =>getDefaultMiddleware({serializableCheck: false,}).concat(logger),
});// you can also create some redux hooks using the above explicit types
export const useAppDispatch = () => useDispatch<AppThunkDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
useAppDispatch
和 useAppSelector
这个,因为类型检查的关系,使用 TS 的前提下几乎是必须的,我之前也有一篇笔记讨论过这个。
helper func
我们实现了一个 createAsyncThunk
的 wrapper,主要用来处理 AppThunkDispatch
需要重复提供类型问题,代码如下:
import { createAsyncThunk, createAction } from '@reduxjs/toolkit';
import { AppThunkDispatch, RootState } from '../';export const createAppAsyncThunk = createAsyncThunk.withTypes<{state: RootState;dispatch: AppThunkDispatch;rejectValue: string;extra: { s: string; n: number };
}>();
slice/index.ts
slice/index.ts 主要就是用来集成一堆的 reducer 让 store 去使用,同时导出所有的 actions,这样让其他地方的 import 干净一些,大致代码如下:
import { example } from './slices/exampleSlice';const reducers = {example,
};export default reducers;export * from './slices/exampleSlice';
slice
这里的 slice 主要是 API 的操作,代码大致如下:
export type IExampleSlice = {loading: boolean;data: any[];error: null | SerializedError;
};const initialState: IExampleSlice = {loading: false,data: [],error: null,
};const uri = '';const exampleSlice = createSlice({name: 'modal',initialState,reducers: {clearState() {return initialState;},},extraReducers(builder) {builder.addCase(fetchExample.pending, () => {return {...initialState,isLoading: true,};});builder.addCase(fetchExample.fulfilled, (state, { payload }) => {// process payload});builder.addCase(fetchExample.rejected, (state, action) => {console.error(action.error);state.error = action.error;});},
});export const fetchExample = createAppAsyncThunk<any,{ payload: Partial<any> },{ state: RootState }
>(`${uri}/post`, async ({ payload }, { dispatch, getState }) => {const res = await wrappedFetchMethod();return res;
});export const { clearState } = exampleSlice.actions;
export const example = exampleSlice.reducer;
其中 IExampleSlice 可以使用 generics 单独抽出来,如:
export type IGenericsSlice<T> = {loading: boolean;data: T[];error: null | SerializedError;
};type ExampleModel = {id: string;name: string;
};export type IExampleSlice = IGenericsSlice<ExampleModel>;
我们项目就是将 API 单独抽出来进行了封装,如果 slice 内有其他需要合并的属性,在定义 ISthSlice
的时候会使用 &
进行合并
其他 slice
其他的 slice 比较灵活,可以根据需求单独神明 type 并且进行返回。
每个 slice 有对应定义的 type 还是挺重要的,尤其是之后 Component 中调用的时候,可以比较方便的提供 intellisense
RTKQ
目前的项目因为数据量的关系(没有做 pagination,并且用户要求不做 pagination),所以决定不使用 RTKQ 在完成 CUD 操作后直接重新拉去数据
不过 RTK 可以 cache query,并且在对应的 query 中对应的功能
也就是一旦出发 CUD 操作,自动调用 retrieve 操作,不需要手动在 await
操作中实现。这一点对于原子性要求更高的 2c 项目中很有用,并且这个功能也取代了一些 redux-saga 可以实现的功能,这也是为什么我没有考虑引入 saga 的原因
组件调用 slice
大致如下:
import React, { useEffect } from 'react';import { useAppDispatch, useAppSelector, RootState } from '../store';
// 所有导出都通过 slices/index,因此相对而言 import 可以稍微干净一些
import { IExampleSlice, fetchExample } from '../store/slices';const Example = () => {const dispatch = useAppDispatch();const { data, error, loading } = useAppSelector<IExampleSlice>((state: RootState) => state.example);const apiCall = async () => {console.log(data, error, loading);const res = await dispatch(fetchExample({payload: {id: '',},}));if (res.meta.requestStatus === 'fulfilled') {// do sth}};useEffect(() => {apiCall();}, []);return <div>Example</div>;
};export default Example;
-
提供一些对于 payload 的检查还是有好处的,比如:
CRUD 中的操作主要都是对于对应的模型操作,因此将上面的 payload 定义改成 Example 的模型,TS 也可以自动对其进行静态检查:
-
useAppSelector 中的类型
主要也是为了 intellisense,如过不提供
<IExampleSlice>
,返回值如下:又或者是一些比较小的 typo,如果不提供类型,TS 是抓不出来的:
提供后:
-
导出方式
这里还是推荐使用
useAppSelector(state => state.someState)
的方式,这个是为了 performance,直接返回整个 state 可能会引起不必须的渲染: -
错误处理
这个其实之前的笔记有提,这里再说一下好了
如果是异步操作,通过 asnc/await 可以直接获取 request 的结果,这样就可以根据接过去具体更新 UI 了:
如:
-
操作成功关闭 modal
-
操作失败继续维持 modal 的开启状态
同时根据返回的 error,在 UI 生成对应的错误信息
-
总结
其实大体上主要还是 redux 的配置比较多一些,其他部分都挺灵活的,而且和业务绑定的比较多,我也不方便说,说了也不一定有参考意义
项目的结构是一个比较风格化的东西,我用的比较喜欢的风格 有点变态的说已经把这个项目变成了我喜欢的样子……? 并不代表这一定是一门项目会用的风格,这是 react 的优点也是缺点
完成 redux 部分的 refactor 之后,下一步考虑的就是使用 lerna 对项目升级,做成一个 micro-frontend 的项目。这也是基于项目本身的特性决定的,我们的项目是一个多地区使用的网页应用,并且不同地区对于想要渲染的数据、显示的页面会有不同的需求。目前的实现就是一旦打包了,所有的东西全都打包 ship 出去,不过操作起来还是不太方便
我的构想是:
- 将所有的 constant/util/components/slices 打包成一个共享的 module
- 每个地区根据不同的需求
- 调用不同的 reducer,生成只包含所需数据的 store
- 实现对应的页面
最后形成一个像 venn diagram 的结构:
而不是将所有的代码打包到一起 ship 出去
相关文章:

React 项目结构小结
React 项目结构小结 简单的记录一下目前 React 项目用的依赖和实现 摸索了大半年了大概构建一套用起来还算轻松的体系……?基本上应该是说可以应对大部分的项目了 使用的依赖 目前项目还在 refactoring 的阶段,所以乱得很,这里是新建一个…...

4.网络之TCP
TCP协议(传输层) 文章目录 TCP协议(传输层)1. TCP报文格式2. TCP相关机制2.1 确认应答机制2.2 超时重传机制2.3 连接管理机制(重点)2.3.1 三次握手2.3.2 四次挥手 2.4 滑动窗口机制2.5 流量控制机制2.6 拥塞控制机制2.7 延迟应答机制2.8 捎带应答机制 3.…...

电池原理与分类
1 电池基础知识 电池目前大量应用于我们的生活中,主要包括3C消费类、动力类、储能类。 图1 电池应用方向 备注:3C指的是计算机(Computer )、通讯(Communication)消费类电子产品(Consumer Electronic)三类…...

Mongoose 开源库--Filesystem(文件系统)使用笔记
一、相关API Mongoose 开源库中也包含 文件系统 相关的 API,如下: 文件虚拟层: struct mg_fs {int (*st)(const char *path, size_t *size, time_t *mtime); // stat filevoid (*ls)(const char *path, void (*fn)(const char *, void *), v…...

新兴初创企业参展招募
一般来说,创业公司的生存率较低,失败率较高。根据不同的数据来源,创业公司的失败率高达 80%-90%。据统计,在中国每年新注册的企业数量超过 100 万家,但能够存活到 5 年以上的企业不足 7%,10 年以上不足 2%。…...

【Linux】Nginx安装使用负载均衡及动静分离(前后端项目部署),前端项目打包
一、Nginx导言 1、引言 Nginx 是一款高性能的 Web 服务器和反向代理服务器,也可以充当负载均衡器、HTTP 缓存和安全防护设备。它的特点是内存占用小、稳定性高、并发性强、易于扩展,因此在互联网领域得到了广泛的使用。 总结出以下三点: 负载均衡&#…...

银行和金融企业为何青睐这8款项目管理工具
银行、金融行业中主流的8款项目管理系统:1.PingCode;2.Worktile;3.Microsoft Project;4.Jira by Atlassian;5.Asana;6.Trello;7.Wrike;8.Teambition。 银行和金融性质的公司在项目管…...
一分钟理解npm run dev 和 npm run serve
前端开发过程中运行Vue项目的时候,有时候使用npm run serve命令可以启动项目,有时候却会报错;有时候使用npm run dev命令可以启动项目,有时候却也会报错。是什么原因造成这种情况呢,原因在于Vue脚手架版本的问题&#…...

HTTP 协议请求头 If-Match、If-None-Match 和 ETag
概述 在 HTTP 协议中,请求头 If-Match、If-None-Match、If-Modified-Since、If-Unmodified-Since、If-Range 主要是为了解决浏览器缓存数据而定义的请求头标准,按照协议规范正确的判断和使用这几个请求头,可以更精准的处理浏览器缓存&#x…...
DAY42 1049.最后一块石头的重量II + 494.目标和 + 474.一和零
1049.最后一块石头的重量II 题目要求:有一堆石头,每块石头的重量都是正整数。 每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x < y。那么粉碎的可能结果如下: …...

uniapp原生插件之安卓华为统一扫码HMS Scan Kit
插件介绍 华为统一扫码服务(Scan Kit)提供便捷的条形码和二维码扫描、解析、生成能力 插件地址 安卓华为统一扫码HMS Scan Kit - DCloud 插件市场 超级福利 uniapp 插件购买超级福利 详细使用文档 详细使用文档 插件申请权限 android.permi…...

数模国赛——多波束测线问题模型建立研究分析
第一次参加数模国赛,太菜了~~~~意难平 问题一 画出与测线方向垂直的平面和海底坡面的交线构成一条与水平面夹角为𝐀的斜线的情况下的示意图进行分析,将覆盖宽度分为左覆盖宽度和右覆盖宽度,求出它们与海水深度和𝐀、…...
[AUTOSAR][诊断管理][ECU][$37] 请求退出传输。终止数据传输的(上传/下载)
文章目录 一、简介二、服务请求报文定义肯定响应支持的NRC三、示例流程Step 1:Step 2:报文示例:Step 1:请求RequestDownload(0x34)服务Step 2:请求TransferData (0x36)服务,传输数据Step 3:请求RequestTransferExit(0x37)服务总结:三、示例代码37_req_transfer_e…...

vue+canvas实现横跨整个页面的动态的波浪线(贝塞尔曲线)
本来写这个特效 我打算用css实现的,结果是一波三折,我太难了,最终没能用css实现,转战了canvas来实现。来吧先看效果图 当然这个图的波浪高度、频率、位置、速度都是可调的,请根据自己的需求调整,如果你讲波浪什么的调大一下 还有有摆动的效果哦。 以下是完整代码 <…...

LeetCode算法题解| 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树
一、LeetCode 669. 修剪二叉搜索树 题目链接:669. 修剪二叉搜索树 题目描述: 给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变…...

直播界很火的无线领夹麦克风快充方案 Type-C接口 PD快充+无线麦克风可同时进行
当前市场上流行一款很火的直播神器,无线领夹麦克风(MIC),应用于网红直播,网课教学,采访录音,视频录制,视频会议等等场景。 麦克风对我们来说并不陌生,而且品类有很多。随…...

Jmeter 汉化中文语言
找到 bin -> jmeter.propertise 修改参数:languageen --> languagazh_CN OK!...

centos9 stream 下 rabbitmq高可用集群搭建及使用
RabbitMQ是一种常用的消息队列系统,可以快速搭建一个高可用的集群环境,以提高系统的弹性和可靠性。下面是搭建RabbitMQ集群的步骤: 基于centos9 stream系统 1. 安装Erlang和RabbitMQ 首先需要在所有节点上安装Erlang和RabbitMQ。建议使用官…...
代码随想录算法训练营第10天|232. 用栈实现队列 225. 用队列实现栈
JAVA代码编写 232. 用栈实现队列 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 实现 MyQueue 类: void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移除…...

线上Kafka集群如何调整消息存储时间
这里是weihubeats,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党 Kafka版本 kafka_2.13-3.5.0 背景 Kafka 默认消息存储时间为7天,实际线上的业务使用Kafka更多的是一些数据统计之类的业务,大多是朝生夕…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动
一、前言说明 在2011版本的gb28181协议中,拉取视频流只要求udp方式,从2016开始要求新增支持tcp被动和tcp主动两种方式,udp理论上会丢包的,所以实际使用过程可能会出现画面花屏的情况,而tcp肯定不丢包,起码…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...