第一部分:前端數據層的探索與實踐(一)
第二部分:前端數據層的探索與實踐(二)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的一個好處,咱們能夠在此次需求業務複雜的時候用它,也能夠在同一個項目裏,需求不復雜的時候甩掉它。很是開心的是它讓我不用再處理那麼多的層級,但願之後在真實的業務場景中能再實踐一次!歡迎朋友們指正此次實踐的問題~
參考資料: