1.数据状态管理工具原理解析
数据状态管理工具的原理是通过mobx创建一个“可观察”的数据,然后使用mobx中名为 observer 的方法包裹组件,将我们封装的组件变成观察者,成为观察者组件。
成为观察者组件,当组件内使用的“可观察数据”发生改变时,会触发render,更新视图。从而实现数据驱动视图的功能。
2.数据状态管理工具封装基础思想
在封装数据状态管理工具的时候,我们可以将其封装成一个类或者一个函数,我采用的是将其封装成一个函数,传参是ds的配置数据,然后返回一个可观察的数据状态管理工具数据实例。
// props中传ds的配置数据
const DataSet = (props)=>{
return observable({...});
}
//使用时先定义一个ds的配置数据,类似
const ds = {
data:[...],
fields:[...],
queryFields:[...],
transport: {...},
}
//组件内使用时
constructor(props) {
super(props);
this.ListDs = DataSet(ds);
}
//封装ant-design表格,使其适配自己封装的DS。
3.简易数据状态管理工具基础属性
DataSet Porps
参数 |
说明 |
data |
源数据 |
autoQuery |
是否自动查询 |
autoCreate |
初始化时自动创建一条记录 |
selection |
表格选择模式,多选(multiple),单选(single),无(false) |
fields |
字段属性数组 |
queryFields |
查询字段属性数组 |
transport |
基础增删改查接口配置 |
events |
事件监听,目前只增加了值更新事件update,后续可以继续加其他事件监听 |
pageSize |
每页数据长度 |
DataSet Values
参数 |
说明 |
records |
所有记录 |
queryDataSet |
查询数据源 |
loading |
加载中状态,用于表格表单查询时设置表格loading |
fields |
ds表格字段配置信息,(目前暂时未对fields做其他处理) |
selection |
选择的模式, 可选值: false 'multiple' 'single' |
validateStatus |
整个records数据的校验状态 |
originalData |
原始数据 |
totalElements |
总数据条数 |
number |
当前查询的第几页 |
size |
每页查询多少条 |
totalPages |
总页面数 |
current |
获取或者设置当前记录 |
selected |
选中的数据 |
queryBar |
查询字段长度 |
dirtyRecords |
状态发生改变的记录 |
created |
新建的记录 |
updated |
更新的记录 |
destroyed |
临时删除的记录 |
dirty |
数据状态是否发生改变 |
DataSet Methods
参数 |
说明 |
toData |
将可观察的记录转化为普通数据 |
create |
创建一条新记录 |
reset |
重置dataset的数据状态 |
query |
查询方法,可传 :(页数, 每页数据长度),例query(1, 20) |
deleteAll |
传参:想要删除的记录集,例:deleteAll(reocrds),删除选中的记录 |
remove |
临时删除某些记录,将状态标为删除状态,通过submit提交进行删除 |
unSelectAll |
表格取消全选 |
selectAll |
表格全选 |
select |
选中某条记录 |
unSelect |
取消选中某条记录 |
submit |
对新增,删除,更新状态的数据进行提交,然后重新查询 |
setQueryParams |
设置查询参数,例:setQueryParams({name: '张三'}) |
DataSet Events
参数 |
说明 |
update |
值更新事件,参数有:字段名name,更新值value,记录record,例:update: ({name, value, record})=>{} |
Record Values
参数 |
说明 |
__status |
状态, 可选值 add | update | delete | sync |
__key |
唯一键,主键 |
selection |
是否选中 |
originalData |
初始数据 |
validateStatus |
校验状态 |
dirty |
数据状态是否发生改变 |
Record Methods
参数 |
说明 |
reset |
重置数据 |
validate |
传dataset的fields参数,进行当前record的数据校验 |
toData |
将可观察的记录转化为普通数据 |
get |
获取某个字段的数据 |
set |
修改某个字段数据 |
Field Props
参数 |
说明 |
name |
字段名 |
label |
字段标签名 |
type |
类型 |
required |
是否必输 |
dataSet |
Lov数据源 |
textField |
Lov显示字段名 |
valueField |
Lov值字段名 |
required |
是否必输 |
defaultValue |
默认值 |
options |
值集数据 |
disabled |
是否可编辑 |
min |
数值输入框最小值校验 |
max |
数值输入框最大值校验 |
validate |
({value}) => string,自定义校验 |
Transport
参数 |
说明 |
read |
查询接口配置 |
update |
更新接口配置 |
create |
新增接口配置 |
destroyed |
删除接口配置 |
4.基于表格基础功能封装DS
(1)为表格提供可观察的数据源。
这里为表格提供了records属性,records属性包含所有的可观察数据,DataSet方法可接收一个ds的data属性,或者使用query方法,调用接口获取源数据。
const DataSet = (props)=>{
const { data = [], transport } = props;
let records: any[] = []; // 记录数据集
const toObserveArrayData=(sourceData)=>{ //将源数据转化为可观察数据
return sourceData.map(item => {
return observable({
data: item,
...other
})
});
}
function query() {// DS.query()方法,通过访问transport中read方法,返回查询的配置参数包含URL和查询参数。
if (transport.read) {
const queryData = this.queryDataSet.current.toData();
const param = transport.read({dataSet, data: queryData});
httpGet(param.url, {...param.data}).then(res => {// httpGet:axios get接口请求调用
if (res && !res.failed) { // 固定后端接口返回格式,如果接口报错,返回falied和message
this.originalData = res.data;
this.records = toObserveArrayData(res.data);// 将接口返回数据转换为可观察数据并赋给records
}
})
}
}
records = toObserveArrayData(data);//初始化records
return observable({ // 返回一个可观察的DS实例对象
records,
query,
...other
});
}
//other: 其他属性和方法。
//ant表格封装使用
this.ListDs = DataSet(ds);
const {records}= this.ListDs;
<Table dataSource ={records} />
(2)实现表格勾选功能
首先为每一条record提供selection(false:未勾选,true:已勾选)属性。
const DataSet = (props)=>{
const toObserveArrayData=(data)=>{ //将源数据转化为可观察数据
return data.map(item => {
return observable({
data,
selection:false,
...other
})
});
}
// 取消所有选择数据
function unSelectAll() {
this.records.map((item: { selection: boolean; }) => item.selection = false);
}
// 选择所有数据
function selectAll() {
this.records.map((item: { selection: boolean; }) => item.selection = true);
}
// 取消选择某些数据,传参:选中的记录集
function unSelect(selectedRecords: any[]) {
selectedRecords.map(item => item.selection = false);
}
// 选择某些数据,传参:选中的记录集
function select(selectedRecords: any[]) {
selectedRecords.map(item => item.selection = true);
}
//初始化records
records = toObserveArrayData(data);
return observable({
records,
get selected() {// 当records发生改变时候,自动计算被选中的数据。
return this.records.filter((item: { selection: boolean; }) => item.selection);
},
unSelectAll,
selectAll,
select,
unSelect,
});
}
//ant表格封装使用
this.ListDs = DataSet(ds);
const {selected}= this.ListDs;
const rowSelection = {
onSelect: (record, selected, selectedRow) => {//控制选中或者取消选中
record.selection = !record.selection;
},
onSelectAll: (selected, selectedRows, changeRows) => {
changeRows.map(record => record.selection = selected);//控制全选
},
columnWidth: '60px',//选择列宽度
fixed: false,
};
<Table
rowSelection={
{
selectedRowKeys: selected.map(record => record.__key),
fixed: true,
type: 'checkbox',
...rowSelection,
}}
/>
(3)实现表格删除功能
给每一条record提供一个__status属性,用来记录当前record的数据状态,包含(add:新增,update: 数据更新,delete删除),同时提供一个dirty属性,判断数据有没有变化过。
表格删除功能,主要是调用DS.deleteAll方法,传参要删除的records
const {confirm} = Modal;
// selectedRecords: 想要删除的记录。
function deleteAll(selectedRecords: any[]) {
confirm({
content: '确认删除选中行?',
okText: '确认',
cancelText: '取消',
onOk: () => {
// 删除已经存在的数据。然后重新查询。首先过滤调新增状态的数据
const deleteData = selectedRecords.filter(item => item.__status !== RecordStatus.add).map(item => item.toData());
//取消全部勾选
this.unSelectAll();
//transport.destroyed写删除配置,destroyed用来存储删除状态的record。
if (transport.destroyed && this.destroyed.length) {
const param = transport.destroyed({dataSet, data: deleteData});
//调用删除接口
deleted(param.url, param.data).then(res => {
if (res && !res.failed) {
notification.success({
message: '操作成功',
placement: 'bottomRight',
})
this.query();//删除完自动重新查询
}
}).catch(() => {
})
}
},
})
}
5.封装代码
(1)DataSet方法代码
import {observable, toJS} from "mobx";
import {RecordStatus} from './enum';
import {deleted, get as httpGet, post, put} from '../utils/http';
import {Modal, notification} from 'antd';
const {confirm} = Modal;
const QueryDataSet = (dataSet: any) => {
const queryDataSet = {
...dataSet,
fields: dataSet.queryFields,
queryFields: [],
transport: {},
autoQuery: false,
data: [{}],
}
return DataSet(queryDataSet, true);
}
const dataSet = (dataSet: { data?: any[]; fields?: any[]; queryFields?: any[]; transport?: any; autoQuery?: boolean; pageSize?: number, selection?: any, event?: any }, isQuery?: boolean) => {
const {
data = [],
fields = [],
transport,
autoQuery = false,
pageSize = 10,
selection = 'multiple',
event
} = dataSet;
let records: any[] = []; // 记录数据集
let queryBar = dataSet.queryFields?.length;
let originalData: any[] = []; //源数据
let loading = false; //异步加载
let queryDataSet = {}; // 查询dataSet
let number = 1; // 当前页为第几页
let size = pageSize; // 每页数据条数
let totalElements = data.length || 0; // 总共多少数据
let totalPages = data.length / size || 0; // 总共多少页
// 初始化
const init = (allData: any[]) => {
//判断是否是已经初始化查询dataSet
if (!isQuery) {
queryDataSet = QueryDataSet(dataSet);
}
originalData = allData;
records = toObserveArrayData(allData);
}
// 查询方法
function query(currentIndex?:any,currentSize?:any) {
if (transport.read) {
const queryData = this.queryDataSet.current.toData();
const param = transport.read({dataSet: this, data: queryData});
this.loading = true;
httpGet(param.url, {
...param.data,
page: currentIndex || this.number,
pageSize: currentSize || this.size
}).then(res => {
this.loading = false;
if (res && !res.failed) {
console.log(res.data);
this.originalData = res.data;
this.records = toObserveArrayData(res.data);
this.totalElements = res.totalElements || 0;
this.number = res.number || 0;
this.totalPages = res.totalPages || 0;
this.size = res.size || 10;
}
})
}
}
// 将普通数据转化为可观察的记录数据
const toObserveArrayData = (arrayData: any[]) => {
return arrayData.map(item => {
const defaultValues: { [x: string]: any; } = {};
fields.map(field => {
if (field.defaultValue !== null && field.defaultValue !== undefined
) {
defaultValues[field.name] = field.defaultValue;
}
})
return observable({
data: {
...defaultValues,
...item,
},
__status: RecordStatus.sync,
__key: Math.random(),
selection: false,
editor: false,
get validateStatus() {
const a = fields.filter(field => this.validate(field).status === 'error');
return a.length <= 0;
},
validate(field: any) {
if (field.required && (this.get(field.name) === null || this.get(field.name) === undefined || this.get(field.name) === '')) {
return {
status: 'error',
type: 'required',
message: `请输入${field.label}!`,
}
}
if ((field.min || field.min === 0) && field.type === 'Number' && (this.get(field.name) === 0 || this.get(field.name))) {
if (this.get(field.name) < field.min) {
return {
status: 'error',
type: 'min',
message: `${field.label}的值必须大于${field.min}!`,
}
}
}
if ((field.max || field.max === 0) && field.type === 'Number' && (this.get(field.name) === 0 || this.get(field.name))) {
if (this.get(field.name) > field.max) {
return {
status: 'error',
type: 'max',
message: `${field.label}的值必须小于${field.max}!`,
}
}
}
if (field.validate && field.validate({value: this.get(f