持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情
使用Mirrorx(Redux)管理数据
如图,这是一个标准的UCF-WEB的项目结构。
ucf-apps中,每个文件夹比如('fls-order','fls-contract'),被称为一个小应用(独立应用),这是一个单页面应用,包括列表及详情等信息。ucf-common是一个公共内容文件夹,其内部包括项目通用的组件,共用的样式及常用的JS工具类。
首先,从数据的通信谈起,一般而言,对于普通的React组件,类组件内,我们将数据存放在state中。父子组件使用props和回调函数的方式实现。其他比较复杂的组件关系。我们使用Redux来处理数据通信。
UCF-WEB工程中使用了mirrorx,MirrorX是一个js库,是基于Redux封装的一种状态机。
所以,针对于每一个小应用,其内部都有一个独立的model.js文件。用于存放该应用内跨组件的数据。
model.js
import { actions } from "mirrorx";
export default {
// 确定 Store 中的数据模型作用域
name: 'mainModel',
// 设置当前 Model 所需的初始化 state
initialState: {
nodeKey: "fls-contract",
funCode: "FLB0000",
},
reducers: {
// 纯函数,相当于 Redux 中的 Reducer,只负责对数据的更新。
updateState(state, data) {
//更新state
return {
...state,
...deepClone(data),
};
},
}
}
关键的 index.js
在该文件内部通过mirrorx的mirrorx方法,将store,router和组件关联起来。
import mirror, { Router, Route, actions, connect } from 'mirrorx';
// 数据模型引入
import model from "./model.js";
mirror.model(model);
import listApp from './list';
let ListContainer = connect((state) => {
return { ...state.mainModel, ...state.routing };
})(listApp);
通用业务数据
这里所说的通用业务数据包括:字典、自定义档案、按钮权限数据、登录详细信息以及自定义参数值。
- 字典:在项目的表单中下拉选择(Select)的选项数据,是通过接口查询,以数组的形式返回到页面中。我们称之为字典。
- 定义档案:类似于字典,但是这些数据是使用该系统的用户,在特定功能节点,根据业务需求,提前录入系统的数据。我们称之为自定义档案。
- 钮权限数据:这是控制页面按钮是否显示的配置数据。
- 录信息:由于
cookie中无法获取用户的完整信息,我们会通过特定接口查询此信息。 - 数值数据:类似于自定义档案的一种数据。
- 他通用数据: 省市县数据,行业门类(大类,中类,小类)等。
以上这些数据,都有如下几个特点:
- 每个小应用都会使用到。
- 通过接口查询,只会读取,不会修改。
- 查询参数固定,查询的结果也是固定的。
存在问题
目前项目的处理方式,在每个小应用中独立处理,通过接口查询,将数据存放在Redux的store中或者存放在根组件的State中。 根据不同场景各自使用。
这样存在以下问题:
- 代码冗余,繁琐。
- 浪费开发时间,效率低。
- 格式及代码风格不统一。
- 后期维护成本高,不易维护。
处理方案
基于以上问题,我们进行了如下优化:
优化思路:将这些共用的数据提取出来,通过通用的CommoModel统一管理,具体应用开发者只需引入并使用,不管数据查询。
Tip:要明确的,这里说的CommonModel是一个跨越小应用的通用组件。和上文说的节点内维护共用数据的MainModel是不同的组件。
创建 CommonModel
在ucf-common中创建commonModel.js文件
export default {
name: "commonModel",
initialState: {
option: {}, // 字典
dicOption: {}, // 自定义档案
userInfo: {}, // 登录人信息
parameterInfo: {}, // 参数值管理配置的数据
authData: [], // 按钮权限数据
divisionArr: [], // 行政区域数据
},
reducers: {
updateState(state, data) {
//更新state
return {
...state,
...deepClone(data),
};
},
},
effects: {
// 加载字典数据
async loadDicItem(param, getState) {
// todo ...
},
// 加载登录人信息
async getLoginInfo(params, getState) {
},
// 加载按钮权限数据
async getBtnAuthInfo(param) {
},
// 加载其他通用数据
}
}
引入并使用
在小应用的入口文件中引入并调用commonModel。 这个这些数据只需要加载是初始化一次,之后不用再调用相关接口。
// 引入mirrorx
import mirror, { Router, Route, actions, connect } from "mirrorx";
// 引入通用Model;
import commonModel from "utils/commonModel";
mirror.model(commonModel);
// 引入当前节点的Model;
import model from "./order/model.js";
mirror.model(model);
// 数据和组件UI关联、绑定
let ListContainer = connect((state) => {
return { ...state.mainModel, ...state.commonModel, ...state.routing };
})(listApp);
// 初始化调用commonModel中的方法;
export class OrderRouter extends Component {
componentDidMount() {
let userId = getCookie("userId") || "";
let paramsCode = ["ABC0000"]; // 查询参数值;
actions.commonModel.loadDicItem({ codeList: dicItem }); // 字典;
actions.commonModel.loadTenantDicItem({ codeList: tenantDicItem }); // 自定义档案;
actions.commonModel.getLoginInfo({ id: userId });
actions.commonModel.getParameterValue({ codeList: paramsCode });
actions.commonModel.getDivisionData(); // 查询行政区划地址
// 按钮权限数据;
actions.commonModel.getBtnAuthInfo({
funcCode: "ABC0000",
r: uuid(),
});
}
}
这个操作之后,我们的通用数据会直接注入到小应用中,节点开发人员只负责相应的业务场景,不在考虑通用数据的查询与维护。
当然,这个commonModel不止负责这些,还有其他用途,我们会另有文章介绍。
另外,有人会说,那这个commonModel中每个方法的调用还需要开发者自行写,并不是完全不用管的。
我想说:看官,您别急,这些还会继续优化,在后面的文章中,我们将学习如何自动生成这些文件。