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;

useAppDispatchuseAppSelector 这个,因为类型检查的关系,使用 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;
  1. 提供一些对于 payload 的检查还是有好处的,比如:

    在这里插入图片描述

    CRUD 中的操作主要都是对于对应的模型操作,因此将上面的 payload 定义改成 Example 的模型,TS 也可以自动对其进行静态检查:

    在这里插入图片描述

    在这里插入图片描述

  2. useAppSelector 中的类型

    主要也是为了 intellisense,如过不提供 <IExampleSlice>,返回值如下:

    在这里插入图片描述

    又或者是一些比较小的 typo,如果不提供类型,TS 是抓不出来的:

    在这里插入图片描述

    提供后:

    在这里插入图片描述

  3. 导出方式

    这里还是推荐使用 useAppSelector(state => state.someState) 的方式,这个是为了 performance,直接返回整个 state 可能会引起不必须的渲染:

    在这里插入图片描述

  4. 错误处理

    这个其实之前的笔记有提,这里再说一下好了

    如果是异步操作,通过 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 出去

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

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

相关文章

有什么好用的CRM客户管理系统?推荐这5大高口碑的CRM系统!

有什么好用的CRM客户管理系统&#xff1f;推荐这5大高口碑的CRM系统&#xff01; 好用的CRM客户管理系统&#xff1a; ①需要进行精细化管理 ②需要专业的管理工具 ③最好能够做到和erp系统和oa系统的集成 授人以鱼不如授人以渔&#xff0c;在给题主推荐crm系统之前&#…

高数笔记06:无穷级数

图源&#xff1a;文心一言 时间比较紧张&#xff0c;仅导图~~&#x1f95d;&#x1f95d; 第1版&#xff1a;查资料、画导图~&#x1f9e9;&#x1f9e9; 参考资料&#xff1a;《高等数学 基础篇》武忠祥 &#x1f433;目录 &#x1f433;常数项级数 &#x1f40b;概要 &…

C++基础——对于C语言缺点的补充(2)

上篇文章中说到&#xff0c;为了解决C语言会出现人为定义的函数和库函数出现重定义的错误&#xff0c;C引入了一个新的概念&#xff0c;即命名空间&#xff0c;通过认为定义命名空间&#xff0c;来解决上述问题。 在本篇文章中&#xff0c;将继续介绍C相对于C语言不足来进行的补…

Harbor企业级Registry基础镜像仓库的详细安装使用教程(保姆级)

Harbor Docker 官方提供的私有仓库 registry&#xff0c;用起来虽然简单 &#xff0c;但在管理的功能上存在不足。 Harbor是vmware一个用于存储和分发Docker镜像的企业级Registry服务器&#xff0c;harbor使用的是官方的docker registry(v2命名是distribution)服务去完成。 ha…

数据结构之堆的实现(图解➕源代码)

一、堆的定义 首先明确堆是一种特殊的完全二叉树&#xff0c;分为大根堆和小根堆&#xff0c;接下来我们就分别介绍一下这两种不同的堆。 1.1 大根堆&#xff08;简称&#xff1a;大堆&#xff09; 在大堆里面&#xff1a;父节点的值 ≥ 孩子节点的值 我们的兄弟节点没有限制&…

5 ip的分配

如上一节所述&#xff0c;需要和其他设备通信&#xff0c;那么需要先配置ip. 1、如何配置ip 1.可以使用 ifconfig&#xff0c;也可以使用 ip addr 2.设置好了以后&#xff0c;用这两个命令&#xff0c;将网卡 up 一下&#xff0c;就可以了 //---------------------------- 使…

AI大模型时代网络安全攻防对抗升级,瑞数信息变革“下一代应用与数据安全”

AI与大模型技术加速普及&#xff0c;安全领域也在以创新视角聚焦下一代应用安全WAAP变革&#xff0c;拓展新一代数据安全领域。近日瑞数信息重磅发布了瑞数全新API扫描器、API安全审计、数据安全检测与应急响应系统及分布式数据库备份系统四大新品。此次发布在延续瑞数信息Bot自…

freertos简单串口

先来完善一下FreeRTOSConfig.h这个配置文件 /*FreeRTOS V9.0.0 - Copyright (C) 2016 Real Time Engineers Ltd.All rights reservedVISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.This file is part of the FreeRTOS distribution.FreeRTOS is …

通信原理板块——时域均衡

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、均衡器 为了减小码间串扰的影响…

放卷开环张力控制(伺服转矩模式应用)

收放卷张力开环闭环控制算法,请参考下面文章链接: PLC张力控制(开环闭环算法分析)_RXXW_Dor的博客-CSDN博客文章浏览阅读4k次,点赞3次,收藏3次。里工业控制张力控制无处不在,也衍生出很多张力控制专用控制器,磁粉制动器等,本篇博客主要讨论PLC的张力控制相关应用和算…

Scala和Play WS库编写的爬虫程序

使用Scala和Play WS库编写的爬虫程序&#xff0c;该程序将爬取网页内容&#xff1a; import play.api.libs.ws._ import scala.concurrent.ExecutionContext.Implicits.global ​ object BaiduCrawler {def main(args: Array[String]): Unit {val url ""val proxy…

06.Oracle数据备份与恢复

Oracle数据备份与恢复 一、通过RMAN方式备份二、使用emp/imp和expdb/impdb工具进行备份和恢复三、使用Data guard进行备份与恢复 一、通过RMAN方式备份 通过 RMAN&#xff08;Oracle 数据库备份和恢复管理器&#xff09;方式备份 Oracle 数据库&#xff0c;可以使用以下步骤&a…

【微服务】mysql + elasticsearch数据双写设计与实现

目录 一、前言 二、为什么使用mysqles双写 2.1 单用mysql的问题 2.2 为什么不直接使用es 2.2.1 非关系型表达 2.2.2 不支持事务 2.2.3 多字段将造成性能低下 三、mysqles双写方案设计要点 3.1 全新设计 VS 中途调整架构 3.2 全表映射 VS 关键字段存储 3.2.1 最大程度…

FPGA高端项目:图像采集+GTP+UDP架构,高速接口以太网视频传输,提供2套工程源码加QT上位机源码和技术支持

目录 1、前言免责声明本项目特点 2、相关方案推荐我这里已有的 GT 高速接口解决方案我这里已有的以太网方案 3、设计思路框架设计框图视频源选择OV5640摄像头配置及采集动态彩条视频数据组包GTP 全网最细解读GTP 基本结构GTP 发送和接收处理流程GTP 的参考时钟GTP 发送接口GTP …

js原型链

什么叫原型链 原型链是js中的核心&#xff0c;原型链将各个属性链接起来&#xff0c;在原型链上面定义&#xff0c;原型链上的其他属性能够使用&#xff0c;原型链就是保证继承 原型链区分 原型链分为显式原型和隐式原型 显式原型&#xff1a;只有函数和构建函数才有显式原型…

spring面试题笔记

SpringBoot 有几种读取配置文件的方式 1.value 必须是bean里才能生效&#xff0c;&#xff0c;final或static无法生效 2ConfigurationProperties注解 ConfigurationProperties是springboot提供读取配置文件的一个注解 注意&#xff1a; 前缀定义了哪些外部属性将绑定到类的字…

AI:62-基于深度学习的人体CT影像肺癌的识别与分类

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

【漏洞复现】Fastjson_1.2.47_rce

感谢互联网提供分享知识与智慧&#xff0c;在法治的社会里&#xff0c;请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞检测3、漏洞验证 1.5、深度利用1、反弹Shell 说明内容漏洞编号漏洞名称Fastjson_1.2.47_远程执行漏…

vue:js中合并对象的方法

目前比较常用的一共有三种 1、使用object.assign() 它可以将一个或多个对象的属性复制到目标对象中&#xff0c;第一个参数就是目标对象&#xff0c;这里举个例子&#xff1a; <template><div>{{data}}</div> </template> <script> export de…

劲升逻辑与安必快、鹏海运于进博会签署合作协议,助力大湾区外贸高质量发展

新中经贸与投资论坛签约现场 中国上海&#xff0c;2023 年 11 月 6 日——第六届进博会期间&#xff0c;由新加坡工商联合总会主办的新中经贸与投资论坛在上海同期举行。跨境贸易数字化领域的领导者劲升逻辑与安必快科技&#xff08;深圳&#xff09;有限公司&#xff08;简称…