网站需要什么服务器,潍坊seo推广,南昌网站建设哪家就好,浙江网站建设模板网站React 项目结构小结
简单的记录一下目前 React 项目用的依赖和实现
摸索了大半年了大概构建一套用起来还算轻松的体系……#xff1f;基本上应该是说可以应对大部分的项目了
使用的依赖
目前项目还在 refactoring 的阶段#xff0c;所以乱得很#xff0c;这里是新建一个…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 states type from reducers
export type RootState ReturnTypetypeof store.getState;// 2. Create a type for thunk dispatch
export type AppThunkDispatch ThunkDispatchRootState, any, AnyAction;// 3. Create a type for store using RootState and Thunk enabled dispatch
export type AppStore OmitStoreRootState, 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 () useDispatchAppThunkDispatch();
export const useAppSelector: TypedUseSelectorHookRootState 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 createAppAsyncThunkany,{ payload: Partialany },{ 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 IGenericsSliceT {loading: boolean;data: T[];error: null | SerializedError;
};type ExampleModel {id: string;name: string;
};export type IExampleSlice IGenericsSliceExampleModel;我们项目就是将 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 } useAppSelectorIExampleSlice((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 divExample/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 出去