这部分包括了一些我们在使用 MobX 开发 Mendix 时发现的最佳实践 这部分是是可选的,你并不强制需要这么做。有很多方式来玩 MobX 和 React ,这只是其中的一种方式。
让我们从 stores 开始。之后我们会讨论 actions 和 React 组件。 Store 可以从任何类 Flux 架构中找到,并且与 MVC 模式中的 controller 对比。 Store 的主要职责是,从组件内移除 逻辑 和 状态 ,抽取到一个独立的可单元测试的部分,这部分可以同时应用于前端和后端两部分。
大多数应用都需要至少两个 Store,一个用于存储 UI 状态,另一个用于存储数据。 分成两个的意义是你可以全局重用和测试,你也可以用于在其他系统中重用。 UI状态Store 通常是否为你的应用定制的,但通常会比较简单。这个Store通常没有太多的逻辑,这对于开发是理想的,因为在开发过程中改变UI状态是很经常的事情。
UI Store中常见存储的信息有:
也许一开始你会尝试用组件内部状态存储这些信息。但后面你会发现,别的地方也需要这些信息。这种情况下,你可以将状态信息控制移到UI State Store。确保这个状态是单例的。 对于同构应用,你可能也想使用同样的默认 Store,这样所有组件都可以按照预期运行。 你可以传递这个 Store,也可以作为模块全局引用。 出于测试的目的,我建议你通过组件树传递。
Store 的例子:
import {observable, computed, asStructure} from 'mobx';
import jquery from 'jquery';
class UiState {
@observable language = "en_US";
@observable pendingRequestCount = 0;
// asStructure 确保 dimensions 对象进行深度比较而改变时,才会发送信号给observer
@observable windowDimensions = asStructure({
width: jquery(window).width(),
height: jquery(window).height()
});
constructor() {
jquery.resize(() => {
this.windowDimensions = getWindowDimensions();
});
}
@computed get appIsInSync() {
return this.pendingRequestCount === 0
}
}
singleton = new UiState();
export default singleton;
你的应用会包含一个或者多个领域 Store。这些会存储你应用的所有数据。 例如todo items, users, books, movies, orders, 或者你所命名的其他领域Store。 你的应用很可能至少有一个领域 Store。
一个领域 Store 应该在你的应用中负责单一的职能。然而一个单一的职能很可能包含多种不同的子类型,经常会是一个(循环的)树形结构 例如:一个领域 Store 用于负责 products,另一个用于负责 orders 和 orderlines。 一个简短的规则是:如果两个元素有天然的关系,则通常他们应该在同一个 Store 中。 所以是一个 Store 管理多个领域对象。
一个领域 Store 的职责有:
每个领域对象都应该是它对应的类(或者构造函数)的表达。 建议将你的数据以非格式化的形式存储。 没有必要将浏览器端的应用状态当做一个数据库来使用。 引用、循环数据结构和实例方法是 JavaScript 中强大的概念。 领域对象允许直接根据来自于其他 Store 的领域对象来推导生成。 记住:我们希望保持我们的行为、视图尽可能地简单,并且管理引用和手动处理垃圾回收是一种退步的行为。 与大多数的 Flux架构不同,Mobx没有必要将你的数据标准化,而是通过更简单的方法去构建你应用中复杂的部分:你的业务规则、行为和 UI。
领域对象可以通过Store来代理它们的逻辑,如果对你的应用而言更方便的话。 你可以使用普通对象来表示一个领域对象,但是通过类的方式,有更多显著的优点:
import {observable, autorun} from 'mobx';
import uuid from 'node-uuid';
export class TodoStore {
authorStore;
transportLayer;
@observable todos = [];
@observable isLoading = true;
constructor(transportLayer, authorStore) {
this.authorStore = authorStore; // 可以处理author的信息的Store
this.transportLayer = transportLayer; // 可以帮助我们发送请求
this.transportLayer.onReceiveTodoUpdate(updatedTodo => this.updateTodoFromServer(updatedTodo));
this.loadTodos();
}
/**
* 从服务端获取所有的 todo's 数据
*/
loadTodos() {
this.isLoading = true;
this.transportLayer.fetchTodos().then(fetchedTodos => {
fetchedTodos.forEach(json => this.updateTodoFromServer(json));
this.isLoading = false;
});
}
/**
* 根据服务端的信息更新一个todo。确保一个todo只会存在一次。
* 构建一个新的todo或者更新一个已存在的todo、或者同步服务端的已被删除的todo的状态。
*/
updateTodoFromServer(json) {
var todo = this.todos.find(todo => todo.id === json.id);
if (!todo) {
todo = new Todo(this, json.id);
this.todos.push(todo);
}
if (json.isDeleted) {
this.removeTodo(todo);
} else {
todo.updateFromJson(json);
}
}
/**
* 在浏览器端和服务端创建一个todo
*/
createTodo() {
var todo = new Todo(this);
this.todos.push(todo);
return todo;
}
/**
* 一个todo被清除了,在浏览器端删除它
*/
removeTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
todo.dispose();
}
}
export class Todo {
/**
* 确保每个todo都有唯一的不可变id。
*/
id = null;
@observable completed = false;
@observable task = "";
/**
* 从 authorStore 引用一个author对象
*/
@observable author = null;
store = null;
/**
* 指示这个对象的改变是否被自动提交给服务端。
*/
autoSave = true;
/**
* 自动存储todo的响应行为的销毁函数。
*/
saveHandler = null;
constructor(store, id=uuid.v4()) {
this.store = store;
this.id = id;
this.saveHandler = reaction(
// 观察这个json中每个被使用的属性
() => this.asJson,
// 如果自动保存是开启的,则自动存储数据到服务器
(json) => {
if (this.autoSave) {
this.store.transportLayer.saveTodo(json);
}
}
);
}
/**
* Remove this todo from the client and server
*/
delete() {
this.store.transportLayer.deleteTodo(this.id);
this.store.removeTodo(this);
}
@computed get asJson() {
return {
id: this.id,
completed: this.completed,
task: this.task,
authorId: this.author ? this.author.id : null
};
}
/**
* Update this todo with information from the server
*/
updateFromJson(json) {
// make sure our changes aren't send back to the server
this.autoSave = false;
this.completed = json.completed;
this.task = json.task;
this.author = this.store.authorStore.resolveAuthor(json.authorId);
this.autoSave = true;
}
dispose() {
// clean up the observer
this.saveHandler();
}
}