React 项目结构小结

news2024/11/30 6:53:28

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.coloradmin.cn/o/1165873.html

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

相关文章

如何理解API?API 是如何工作的呢?

大家可能最近经常听到 API 这个概念&#xff0c;那什么是API&#xff0c;它又有什么特点和好处呢&#xff1f; wiki 百科镇楼 APIs are] a set of subroutine definitions, protocols, and tools for building application software. In general terms, it’s a set of clear…

ZKP10.2 Efficient Recursion via Statement Folding (Nova)

ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 10: Recursive SNARKs, Aggregation and Accumulation (Dan Boneh) 10.3 Efficient Recursion via Statement Folding: Nova, Supernova, and generalizations The difficulty with full recursion Prover P needs to build a…

严重的软件漏洞,你都知道和修复了吗?

有一些漏洞,影响深远而且影响重大。以下的严重软件漏洞,你都知道和修复了吗? 1. Log4Shell Log4Shell软件漏洞存在于Apache Log4j中,这种流行的Java日志框架被全球数千万人使用。 2021年11月,阿里云安全团队成员陈兆军发现了一个严重的代码漏洞。陈兆军最先注意到了Mi…

怎样提取视频提取的人声或伴奏?

有些小伙伴们进行音视频创作时&#xff0c;可能会需要提取音频的人声或者是伴奏。这里给大家推荐一个音分轨人声分离软件&#xff0c;支持一键提取音频人声和一键提取伴奏功能&#xff0c;可批量导入文件同步提取&#xff0c;简单高效&#xff0c;是音视频创作者的不二选择&…

分体式离子风刀和整体式离子风刀分别有哪些优缺点

离子风刀是一种利用高速旋转的离子风扇产生的离子风来清洁和干燥物体表面的设备。根据离子风扇的安装方式&#xff0c;离子风刀可以分为分体式离子风刀和整体式离子风刀。下面是它们各自的优缺点&#xff1a; 分体式离子风刀的优点&#xff1a; 安装方便&#xff1a;分体式离子…

首发scitb包,一个为制作统计表格而生的R包

目前&#xff0c;本人写的第3个R包scitb包已经正式在R语言官方CRAN上线&#xff0c;scitb包是一个为生成专业化统计表格而生的R包。 可以使用以下代码安装 install.packages("scitb")scitb包对我而言是个很重要的R包&#xff0c;我的很多想法需要靠它做平台来实现&a…

bean product not found within scope之解决方法

错误原因&#xff1a; 获取bean中的商品信息时报错 解决方法&#xff1a; 修改为下面代码即可&#xff1a; <jsp:useBean id"product" class"com.model.Product" scope"session"></jsp:useBean>

TP858 3BSE018138R1 具有高性能CPU的工业PC技术

TP858 3BSE018138R1 具有高性能CPU的工业PC技术 为了充分利用新电脑的扩展图形功能&#xff0c;如DirectX&#xff0c;Beckhoff Automation重新设计了TwinCAT automation软件套件中的Scope工具。这为TwinCAT用户在灵活的软件环境中提供了一系列令人印象深刻的测量技术。改进的…

泊车功能专题介绍 ————智能泊车辅助系统性能要求及试验方法(GB/T 41630-2022)

文章目录 术语系统状态转换及信息提示系统非激活状态停车位搜索状态泊车辅助状态系统退出条件系统故障响应 性能要求避撞要求挡位调整次数要求平行停车位&#xff08;空间车位&#xff09;结束位置垂直停车位&#xff08;空间车位&#xff09;结束位置平行停车位&#xff08;线…

C++标准模板(STL)- 类型支持 (类型属性,is_pod,is_trivially_copyable,is_standard_layout)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

Apache Doris 开源最顶级基于MPP架构的高性能实时分析数据库

背景介绍 Apache Doris是一个基于MPP架构的易于使用&#xff0c;高性能和实时的分析数据库&#xff0c;以其极高的速度和易用性而闻名。海量数据下返回查询结果仅需亚秒级响应时间&#xff0c;不仅可以支持高并发点查询场景&#xff0c;还可以支持高通量复杂分析场景。 这些都…

如何使用Scrapy提取和处理数据

目录 一、安装和设置Scrapy 二、创建爬虫 三、提取数据 四、处理数据 五、存储数据 六、进阶操作 七、注意事项 总结 Scrapy是一个强大且灵活的Python库&#xff0c;用于创建网页爬虫&#xff0c;提取和处理数据。本文将为您深入讲解如何使用Scrapy进行数据处理&#x…

AI正在改变人类社会 - 内容行业的衰落

现在的 AI 技术&#xff0c;每天都在进化。我有一种感觉&#xff0c;普通人大概没意识到&#xff0c;它马上就要改变人类社会了。 历史上&#xff0c;这种事一再发生。在你不知不觉中&#xff0c;某些大事件悄悄酝酿&#xff0c;突然就冲击到了你的生活&#xff0c;将你的人生…

TensorRT加速的原因:量化+网络结构优化

文章目录 1. TensorRT 简介2. TensorRT 加速的原理量化网络结构优化 3. TensorRT 的 工作流程TensorRT引擎的构建&#xff1a;BuildTensorRT引擎的推理&#xff1a;Infer 1. TensorRT 简介 TensorRT可以帮助你把训练好的AI模型&#xff0c;部署到边端Nvidia的设备&#xff0c;…

结合组件库实现table组件树状数据的增删改

如图所示&#xff0c;可以实现树状数据的新增子项&#xff0c;新增平级&#xff0c;删除。主要用到了递归 代码&#xff1a; <template><el-table :data"tableData" style"width: 100%; margin-bottom: 20px" row-key"id" border def…

大促期间如何监测竞品数据

无论在什么时候&#xff0c;竞品的数据都是品牌非常关注的&#xff0c;大促当然也不例外&#xff0c;所以准确监测到竞品数据应该如何分析也很关键&#xff0c;通过分析竞品&#xff0c;品牌可以获取非常多有价值的内容&#xff0c;如竞品王牌产品的分析、行业分析报告等。 力维…

JAVA毕业设计108—基于Java+Springboot的OA办公自动化人事管理系统(源码+数据库)

基于JavaSpringboot的OA办公自动化人事管理系统(源码数据库)108 一、系统介绍 本系统分为员工、部门经理、人事、管理员四种角色(角色菜单可以自行分配) 用户&#xff1a; 登录、考勤管理、申请管理、任务管理、日程管理、工作计划管理、文件管理、笔记管理、邮件管理、通讯…

如何在 Photoshop 中制作水晶效果

如何在 Photoshop 中仅使用一些智能滤镜快速轻松地制作水晶效果 1.如何在 Photoshop 中创建快速背景 步骤1 首先&#xff0c;让我们从一个新的画布开始&#xff0c;使用颜色填充图层填充柔和的紫罗兰色。通过选择图层并右键单击 > 转换为智能对象&#xff0c;将该颜色填充…

什么是Node.js的流(stream)?它们有什么作用?

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

设计模式第一课-单例模式(懒汉模式和饿汉模式)

单例模式 个人理解&#xff1a;单例模式实际就是通过类加载的方式获取到一个对象&#xff0c;并且保证这个对象在使用中只有一个&#xff0c;不允许再次被创建 一、懒汉模式 1、懒汉模式的基础写法 代码解释&#xff1a; &#xff08;1&#xff09;、编写LazySingleton类的…