第一部分:前端数据层的探索与实践(一)
第二部分:前端数据层的探索与实践(二)html
数据模型是Redux-ORM的核心。根据实际业务,咱们会定义不少的数据模型,经过定义模型的静态属性字段field对实体进行建模。一个模型表明一张表,模型的名字用静态属性modelName定义,模型的属性用静态属性field定义,这些数据模型都继承于Model。模型的属性field多是纯属性,也多是指向另外一张表的关系属性,一般有三种关系:一对多fk,一对一oneToOne,多对多many。前端
咱们说一个模型表明一张表,那么一个模型实例咱们能够认为这就是数据库中的一条记录。但模型实例并非咱们真正的底层对象,它只是一个由属性items/itemById组成的字面量对象,要访问真正的底层对象应使用ref属性。git
在reducer中对Model进行操做时,Redux-ORM会把action放入队列,直到调用session.state才会让队列中的action顺序执行,直到获得最终结果。github
关系对象映射器。在ORM上注册Model,使用ORM生成session。在整个应用中,ORM一般是以单例的形式存在。在注册Model时,Redux-ORM会判断Model是否有多对多关系,若是有,会自动生成穿越模型(through models),这就像数据库中的关系表,里面存放着关联条目的id和这条对应关系自己的id。数据库
Session用于与模型类数据进行交互。也就是说在对数据进行增删改查时,一般要使用模型来操做,此时咱们想要获取Redux-ORM中的模型,就必定要从session实例中提取对应的模型实例,而不要直接从定义Model类的模块中导入,在操做完成后,要返回当前session实例的数据库状态state,以更新store。建立session实例,一般用orm.session(state)。若是在模型类中定义reducer,那么session会以第四个参数传入,前三个参数分别是state/payload/当前模型类的绑定版本。redux
先看一下实现效果,顺便贴上代码库地址:redux-orm-dvaapi
用我本身的理解,我认为实践应该有这四步:bash
整个demo我是在dvajs的基础上作的,若是习惯使用redux,能够看看Redux-ORM做者的demo,已经是很是详细,但注意,这个demo仍是使用的0.9如下的api,本文是基于0.9以上版本,会有一些api差别,但核心是同样的。session
定义Student/Teacher/Grade/Class模型,Student/Teacher都是最基础的结构,重点在Grade/Class,Grade和Class是一对多的关系,因此用fk
,Class和Teacher是多对多的关系(注意会自动生成穿越模型ClassTeachers),因此用many
,Class 和Student 是一对多的关系,也用fk
。post
// src/models/models.js
import { attr, many, fk } from 'redux-orm';
import PropTypes from 'prop-types';
export class Class extends CommonModel {
static modelName = 'Class';
static fields = {
name: attr(),
teachers: many('Teacher'),
students: fk('Student'),
};
static propTypes = {
name: PropTypes.string.isRequired,
teachers: PropTypes.arrayOf(PropTypes.number),
students: PropTypes.arrayOf(PropTypes.number),
};
static defaultProps = {
name: '',
teachers: [],
students: [],
}
}
export class Grade extends CommonModel {
static modelName = 'Grade';
static fields = {
name: attr(),
classes: fk('Class'),
};
static propTypes = {
name: PropTypes.string.isRequired,
classes: PropTypes.arrayOf(PropTypes.number),
};
static defaultProps = {
name: '',
classes: [],
}
}
复制代码
全部的Model都继承于CommonModel
,这是一个自定义的父类,提取static generate
方法。这个方法根据传入的属性默认值newAttributes
,生成一个新的Model实例。
// src/models/models.js
import { attr, many, fk } from 'redux-orm';
class CommonModel extends Model {
static generate(newAttributes = {}) {
this.defaultProps = this.defaultProps || {};
const combinedAttributes = {
...this.defaultProps,
...newAttributes,
};
return this.create(combinedAttributes);
}
}
复制代码
定义orm,这个没啥好说的,处处都会用到orm这个单例。
// src/models/orm.js
import { ORM } from 'redux-orm';
import { Student, Teacher, Grade, Class } from './models';
const orm = new ORM();
orm.register(Student, Teacher, Grade, Class);
export default orm;
复制代码
定义selector。定义state以前,咱们先看selector的基本用法。reselect是一个选择库,简单来讲,就是用它能够组合选择,而且它能够帮你避免重复渲染。用法上记住两个概念,一是input selector
,根据传入的参数,作一些计算返回结果,二是following selector
,以input selector为参数,获得最终结果。
下面是最基本的用法,从Model中获取真实数据。
// src/routes/selectors.js
import { createSelector } from 'reselect';
import orm from '../models/orm';
const selectSession = entities => orm.session(entities);
export const selectTeacher = createSelector(
selectSession,
({ Teacher }) => {
return Teacher.all().toRefArray();
},
);
复制代码
复杂一点的,Class
下有多个Student
,在这里处理好数据,以便在组件中渲染出学生的名字。Grade
下有多个Class
,同理。
export const selectGrade = createSelector(
selectSession,
({ Grade, Class }) => {
return Grade.all().toRefArray().map(v => {
if (v.classes && v.classes.length !== 0) {
return {
...v,
classes: v.classes.map(stuId => {
const ModelInstance = Class.withId(stuId);
return ModelInstance ? ModelInstance.ref : '';
})
};
}
return v;
});
},
);
export const selectClass = createSelector(
selectSession,
({ Class, Student }) => {
return Class.all().toRefArray().map(v => {
if (v.students && v.students.length !== 0) {
return {
...v,
students: v.students.map(stuId => {
const studentModel = Student.withId(stuId);
return studentModel ? studentModel.ref : '';
})
};
}
return v;
});
},
);
复制代码
这个时候咱们加载Grade
默认数据,就能够先看到简单的渲染结果,是这样。
editingOrm
先无论,先看
orm.getEmptyState()
,会拿到注册好的Model数据。
// src/models/example.js
import orm from './orm';
export default {
namespace: 'example',
state: {
orm: orm.getEmptyState(),
editingOrm: orm.getEmptyState(),
selectedClassId: '',
selectedGradeId: '',
},
}
复制代码
一、如何初始化模型数据呢,主要是使用static upsert
方法,将一条一条的数据插入数据库便可,而后返回session.state
更新state.orm
。下面是reducer:
insertEntities(state, { payload: {data, modelType} }) {
const session = orm.session(state.orm);
const ModelClass = session[modelType];
data.forEach(v => {
ModelClass.upsert(v);
})
return {
...state,
orm: session.state,
};
},
复制代码
二、如何清空模型数据呢,主要是使用static delete
,能够清空整个模型,也能够这样删除某个模型实例ModelClass.withId(id).delete()
。
delete(state, { payload: { modelType } }) {
const session = orm.session(state.orm);
const ModelClass = session[modelType];
ModelClass.delete();
return {
...state,
orm: session.state,
};
},
复制代码
三、在编辑模型数据时,咱们一般会有取消/保存两个操做,点击取消,编辑数据不该用,点击保存,才将编辑数据应用于被编辑的条目。因此会有editingOrm
这样的state,用于存放编辑数据。注意:Class与Teacher是多对多的关系,因此咱们须要对teachers作单独处理,使用update
对Class
进行更新,能够触发生成editingOrm
下的穿越模型数据ClassTeachers
。
selectClass(state, { payload: { id }}) {
const session = orm.session(state.orm);
const editingSession = orm.session(state.editingOrm);
const { Class, ClassTeachers } = session;
const classData = Class.withId(id).ref;
const { Class: EditingClass } = editingSession;
const modelInstance = EditingClass.generate(classData);
const classTeachers = ClassTeachers.filter({ fromClassId: id }).all().toRefArray().map(v => v.toTeacherId);
modelInstance.update({teachers: classTeachers});
return {
...state,
selectedClassId: id,
editingOrm: editingSession.state,
}
},
复制代码
四、更新模型数据,使用static update
。这里使用的editingOrm
,由于在更新class数据时,是把这一份待更新数据放入了editingOrm
,等到保存的时候再应用于orm
。
updateSelectedClass(state, { payload }) {
const editingSession = orm.session(state.editingOrm);
const { Class } = editingSession;
const modelInstance = Class.withId(state.selectedClassId);
modelInstance.update(payload);
return {
...state,
editingOrm: editingSession.state,
}
},
复制代码
五、应用编辑数据到被编辑条目,这就和3相似了,只是如今是将editingOrm
的数据写到orm
。
saveClass(state) {
const id = state.selectedClassId;
const session = orm.session(state.orm);
const editingSession = orm.session(state.editingOrm);
const { Class } = session;
const { Class: EditingClass, ClassTeachers } = editingSession;
const editingData = EditingClass.withId(id).ref;
const modelInstance = Class.withId(id);
const classTeachers = ClassTeachers.filter({ fromClassId: id }).all().toRefArray().map(v => v.toTeacherId);
modelInstance.update({
...editingData,
teachers: classTeachers,
})
return {
...state,
orm: session.state,
}
},
复制代码
到这儿,整个代码就分析完了。不知道朋友们有没有发现很是微妙的事情,reducer仿佛老是能够复用的,只要咱们传入指定的ModelType
!不过我在这儿就没有继续延展了,有兴趣你们能够本身再研究下,这就是你某一天写重复代码终于写烦的时候想作的事了。
其实用不用redux-orm仍是取决于项目的复杂程度,并且也不须要每一个组件都必须用,我以为这是redux-orm的一个好处,咱们能够在此次需求业务复杂的时候用它,也能够在同一个项目里,需求不复杂的时候甩掉它。很是开心的是它让我不用再处理那么多的层级,但愿之后在真实的业务场景中能再实践一次!欢迎朋友们指正此次实践的问题~
参考资料: