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 出去

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【使用Python编写游戏辅助工具】第一篇:概述

引言 欢迎阅读本系列文章&#xff0c;本系列将带领读者朋友们使用Python来实现一个简单而有趣的游戏辅助工具。 写这个系列的缘由源自笔者玩了一款游戏。正巧&#xff0c;笔者对Python编程算是有一定的熟悉&#xff0c;且Python语言具备实现各种有趣功能的能力&#xff0c;因…

基于单片机的可穿戴个人健康监测仪-智能手环

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、方案的设计与论证2.1设计任务及要求2.2 模块技术和方法综述2.3 设计可能遇到的困难 二、 系统总体框架3.1 硬件设计 三 软件部分4.1 主程序流程框 四、 结论五、 文章目录 概要 近几年智能化的不断发展&#…

3000 台 Apache ActiveMQ 服务器易受 RCE 攻击

超过三千个暴露在互联网上的 Apache ActiveMQ 服务器容易受到最近披露的关键远程代码执行 (RCE) 漏洞的影响。 Apache ActiveMQ 是一个可扩展的开源消息代理&#xff0c;可促进客户端和服务器之间的通信&#xff0c;支持 Java 和各种跨语言客户端以及许多协议&#xff0c;包括…

HiveSQL中last_value函数的应用

一、背景 在以下数据中如何实现对每一个列按照更新时间取最新的非null值&#xff1f; 1 a a null 202301 202301 1 b b null null 202302 1 null c null null 202303 1 d null null null 202304如何实现…

Nginx的location优先级和重定向

Nginx的location有优先级级和匹配方式&#xff1a; 在http模块有server,在server模块才有location,location匹配的是uri /test /image 在一个server当中有多个location,如何来确定匹配哪个location。 Nginx的正则表达式&#xff1a; ^:字符串的起始位置 $:字符串的结束位…

Spring Boot 常见面试题

目录 1.Spring Boot 快速入门什么是 Spring Boot&#xff1f;有什么优点&#xff1f;Spring Boot 与 Spring MVC 有什么区别&#xff1f;Spring 与 Spring Boot 有什么关系&#xff1f;✨什么是 Spring Boot Starters?Spring Boot 支持哪些内嵌 Servlet 容器&#xff1f;如何设…

【Excel密码】四个方法,设置excel表格只读模式

Excel文件想要设置成只读模式&#xff0c;其实很简单&#xff0c;今天给大家分享四个excel设置只读模式的方法。 方法一&#xff1a;文件属性 右键点击文件&#xff0c;查看文件属性&#xff0c;在属性界面&#xff0c;勾选上只读属性就可以了。 方法二&#xff1a;始终以只读…

微信-Native支付(扫二维码支付)工具类 2023最新保姆教程

0、照着微信开发文档 取到证书、秘钥等 好几个key 1、获取商户号merchantId 账户中心->商户信息->微信支付商户号 3、获取商户证书序列号merchantSerialNumber 账户中心->API安全->API证书管理 5、获取appID 产品中心->AppID账号管理 1、这个链接教你获取各…

基于卷积神经网络的抗压强度预测,基于卷积神经网络的抗折强度预测

目录 背影 卷积神经网络CNN的原理 卷积神经网络CNN的定义 卷积神经网络CNN的神经元 卷积神经网络CNN的激活函数 卷积神经网络CNN的传递函数 卷积神经网络CNN抗压强度预测 完整代码:基于卷积神经网络的抗压强度和抗折强度预测,基于CNN的抗压强度和抗折强度预测(代码完整,数据…

CAN报文的信号和信号组传递的意义

CAN将数据发送到COM层&#xff0c;在这个过程中报文是如何传递的&#xff1f; 0x105指的是一帧CAN报文&#xff0c;信号组指的是一帧CAN报文里的所有数据&#xff0c;信号指的是一帧CAN报文里的每一个信号&#xff0c;PDU代表了一帧CAN报文&#xff0c;它由报文ID&#xff08;I…

MySQL(7):单行函数

不同DBMS函数的差异 内置函数&#xff1a; 系统内置的通用函数。 自定义函数&#xff1a; 根据自己的需要编写的函数。 大多数 DBMS 使用&#xff08;||&#xff09;或者&#xff08;&#xff09;来做拼接符&#xff0c;而在 MySQL 中的字符串拼接函数为concat()。 大部分 D…

Kafka(二)消息系统设计

文章目录 前言整体设计时序图时序图解释 最后 前言 当多个系统之间通过Kafka来解耦时&#xff0c;在系统设计初期&#xff0c;基本的要求都是相似的&#xff0c;只不过是消费消息时的业务逻辑可能不同。 本文以业务系统和邮件系统解耦作为示例。业务系统需要发送邮件时&#…

后端接口接收对象和文件集合,formdata传递数组对象

0 问题 后端接口需要接收前端传递过来的对象和文件集合&#xff1b;对象中存在数组对象 1 前端和后端 前端只能使用formdata来传递参数&#xff0c;后端不使用RequestBody注解 2 formdata传递数组对象 2.1 多个参数对象数组 addForm: {contactInfo: [{contactPerson: ,…

黑客入门 15 个必杀技能!

互联网新兴技术不断涌现&#xff0c;在给人类带来巨大财富和便捷的同时&#xff0c;也带来了非常严峻的网络安全问题。 侵害个人隐私、侵犯知识产权、网络犯罪等时有发生&#xff0c;网络监听、网络攻击、网络恐怖主义活动等成为**全球公害。 习主席在全国网络安全和信息化工…

[Docker]四.Docker部署nodejs项目,部署Mysql,部署Redis,部署Mongodb

一.部署nodejs项目,映射端口,挂载数据卷 可以到https://hub.docker.com/去搜索node镜像,然后下载,也可以直接通过docker pull node下载镜像,然后用这个node镜像启动容器node,这样系统就集成了node服务了,在这里挂载www/node目录到容器中,并指定端口映射,运行nodejs程序,安装npm…

PMIC、电源管理MAX77646ANP、MAX77647AANP、MAX77675AEWE、MAX77847AEWL DC-DC 开关稳压器

一、MAX77646ANP、MAX77647AANP 低IQ SIMO PMIC支持原电池应用的1.8V工作电压 MAX77646/MAX77647为尺寸和效率至关重要的低功耗应用提供电源解决方案。该IC集成单电感多输出(SIMO)降压/升压稳压器&#xff0c;可通过单个电感提供三个可独立编程的电源轨&#xff0c;尽可能地减…

原地封神!一个只用套模板即可制作电子相册的网站

对于忙碌的年轻人来说&#xff0c;一键操作的模板意味着无需复杂的操作步骤&#xff0c;就能轻松制作出精美的电子相册。 但是一个好的工具也是事关重要&#xff0c;最近发现了一款非常适合年轻人的模板---FLBOOK在线制作电子杂志平台&#xff0c;只需要找到合适的模板即可制作…

计算虚拟化2——内存虚拟化

目录 物理机内存访问过程 虚拟地址VA和物理地址PA概念 MUU实现VA到PA所使用的映射表 内存虚拟化类型 内存软件辅助虚拟化 内存硬件辅助虚拟化 内存虚拟化-内存超分配 内存共享 内存置换 内存气泡 物理机内存访问过程 内存的基本知识 内存都是从物理地址0开始的&…

2023腾讯云双11优惠3年轻量2核2G4M服务器366.6元,三年价哦!

腾讯云3年轻量应用服务器配置为2核2G4M带宽、50GB SSD系统盘双11优惠价格366.6元三年、108元一年&#xff0c;只是限制月流量&#xff0c;套餐自带300GB月流量。腾讯云百科txybk.com分享2023腾讯云双11优惠活动3年轻量2核2G4M带宽优惠价格、购买条件&#xff1a; 3年轻量2核2G…

ubuntu无网络连接,没有网络标识,快速解决方法

在这里插入代码片当我们装虚拟机的时候&#xff0c;需要用到网络时发现没有网络连接&#xff0c;且右上角没有网络标识符&#xff0c;这时只需要简单的输入一下三个命令即可 sudo nmcli networking offsudo nmcli networking onsudo service network-manager restart然后重启客…