本文对《Ten minute introduction to MobX and React》一文进行简单翻译,与大家分享,欢迎交流指正。
MobX是简单、可扩展的、经过实战检验过的状态管理解决方案。本教程将在10分钟内教会你MobX的所有重要概念。MobX是一个独立的库,但是大多数人都将它与React一同使用,本教程也侧重于这种组合。
状态是每个应用的核心,创建有bug的、不可管理的应用程序的最快方法就是生成不一致的状态或是与周围本地变量不一样的状态。因此,很多状态管理方案试图通过限制用户修改状态的方式,例如使状态变得不可变。但是这种做法产生了新的问题:数据需要规范化、引用的完整性得不到保证、一些强大的概念(如原型)变得几乎不能使用。
MobX通过解决根本问题使状态管理再次变得简单:抑制不一致状态的产生。实现的策略很简单:确保所有能够从应用状态推导出来的东西,都将被自动推导出来。
从概念上讲,MobX将应用程序视为电子表格。
有足够的理论说明,直接行动可能比仔细阅读上面的东西收获更多。为了创意起见,我们从一个非常简单的任务商店开始。下面是一个非常简单的TodoStore,维持了todos集合,这里MobX没有参与。
class TodoStore {
todos = [];
get completedTodosCount() {
return this.todos.filter(
todo => todo.completed === true
).length;
}
report() {
if (this.todos.length === 0)
return "";
return `Next todo: "${this.todos[0].task}". ` +
`Progress: ${this.completedTodosCount}/${this.todos.length}`;
}
addTodo(task) {
this.todos.push({
task: task,
completed: false,
assignee: null
});
}
}
const todoStore = new TodoStore();
我们刚刚用todos集合创建了一个todoStore实例,是时候用一些对象填充todoStore了。为了确保看到改变的效果,我们调用todoStore。每次改变后报告并log(控制台打印)。注意,报告总是故意只打印第一个任务,这使得这个示例有点人为,但正如您将在下面看到的,它很好地演示了MobX是动态的依赖跟踪。
todoStore.addTodo("read MobX tutorial");
console.log(todoStore.report());
todoStore.addTodo("try MobX");
console.log(todoStore.report());
todoStore.todos[0].completed = true;
console.log(todoStore.report());
todoStore.todos[1].task = "try MobX in own project";
console.log(todoStore.report());
todoStore.todos[0].task = "grok MobX tutorial";
console.log(todoStore.report());
到目前为止,这段代码没有什么特别之处。但是,如果我们不必显式调用report,而只需声明希望在每个state更改时调用它,又会怎样呢? 这将避免我们在代码里可能影响report的任何地方调用report。我们想确定最新的报告打印好了,又不想因为组织这些而烦恼。
幸运的是,这正是MobX可以为您做的——自动执行只依赖于state的代码。如此一来我们的报表函数就会自动更新,就像电子表格中的图表一样。为了实现这一点,TodoStore必须变得可观察,以便MobX能够跟踪正在进行的所有更改。让我们修改类来达到这个目的。
另外,completedTodosCount属性可以从todo列表中自动派生。通过使用@observable和@computed这两个修饰符,我们可以引入对象的可观察属性:
class ObservableTodoStore {
@observable todos = [];
@observable pendingRequests = 0;
constructor() {
mobx.autorun(() => console.log(this.report));
}
@computed get completedTodosCount() {
return this.todos.filter(
todo => todo.completed === true
).length;
}
@computed get report() {
if (this.todos.length === 0)
return "";
return `Next todo: "${this.todos[0].task}". ` +
`Progress: ${this.completedTodosCount}/${this.todos.length}`;
}
addTodo(task) {
this.todos.push({
task: task,
completed: false,
assignee: null
});
}
}
const observableTodoStore = new ObservableTodoStore();
就是这样!我们将一些属性标记为@observable,以向MobX发出信号,表示这些值可以随时间变化。通过@computed修饰符来识别这些从state派生出来的计算。
目前还没有使用pendingrequest和assignee属性,但是将在本教程后面使用。为了简洁起见,本页的所有示例都使用了ES6、JSX和decorator。但是别担心,MobX中的所有装饰器都有一个ES5与之对应。
在构造函数中,我们创建了一个小函数,它打印report并将其包装在autorun中。Autorun创建一个只运行一次的reaction,在此之后,当函数中使用的任何observable数据发生变化时,进行自动重新运行。因为report使用了observable todos属性,所以它会在适当的时候打印报表。下一个清单中演示了这一点。这里只需按下运行按钮:
observableTodoStore.addTodo("read MobX tutorial");
observableTodoStore.addTodo("try MobX");
observableTodoStore.todos[0].completed = true;
observableTodoStore.todos[1].task = "try MobX in own project";
observableTodoStore.todos[0].task = "grok MobX tutorial";
很有趣,对吗?report确实是自动、同步打印的,而且没有泄漏中间值。如果仔细研究日志,您会发现第4行没有生成新的日志行。因为报告【实际上】并没有因为重命名而更改,尽管备份数据更改了。另一方面,更改第一个todo的名称确实会更新报告,因为该名称在报告中被积极使用。这很好地说明,不仅todos数组被autorun观察到,而且todo项内部的单个属性也被观察到。
好的,到目前为止,我们做了一个傻瓜式的report,是时候围绕这个相同的存储构建一个可交互的用户界面了。React组件(尽管它们的名称不同)并不是现成的可交互组件。MobX-react包中的@observer修饰符修复了在autorun中包装React组件render方法,自动保持组件与状态同步的问题。这在概念上与我们之前对report所做的没有区别。
下一个清单定义了一些React组件。MobX中唯一的东西是@observer修饰器。这足以确保在相关数据发生变化时,每个组件单独重新呈现。您不再需要调用setState,也不需要知道如何使用需要配置的选择器或更高阶组件订阅应用程序状态的适当部分。基本上,所有组件都变得智能化了。然而,它们是以一种愚蠢的、声明性的方式定义的。
按下“Run”按钮,可以看到下面正在运行的代码。这个列表是可编辑的,所以您可以随意使用它。例如,删除所有@observer调用,或者只删除修饰TodoView的调用。每次呈现组件时,右侧预览中的数字都会突出显示。
@observer
class TodoList extends React.Component {
render() {
const store = this.props.store;
return (
{ store.report }
{ store.todos.map(
(todo, idx) =>
) }
{ store.pendingRequests > 0 ? : null }
(double-click a todo to edit)
);
}
onNewTodo = () => {
this.props.store.addTodo(prompt('Enter a new todo:','coffee plz'));
}
}
@observer
class TodoView extends React.Component {
render() {
const todo = this.props.todo;
return (
{ todo.task }
{ todo.assignee
? { todo.assignee.name }
: null
}
);
}
onToggleCompleted = () => {
const todo = this.props.todo;
todo.completed = !todo.completed;
}
onRename = () => {
const todo = this.props.todo;
todo.task = prompt('Task name', todo.task) || todo.task;
}
}
ReactDOM.render(
,
document.getElementById('reactjs-app')
);
下一个清单很好地显示了我们只需要改变数据而不做任何其他事情。MobX将从存储中的状态自动派生和更新用户界面的相关部分。
const store = observableTodoStore;
store.todos[0].completed = !store.todos[0].completed;
store.todos[1].task = "Random todo " + Math.random();
store.todos.push({ task: "Find a fine cheese", completed: true });
// etc etc.. add your own statements here...
到目前为止,我们已经创建了可观察对象(原型对象和普通对象)、数组和基元。您可能想知道,MobX中的引用是如何处理的?我的state可以形成一个图吗?在前面的清单中,您可能已经注意到todos上有一个assignee(受让人?)属性。让我们通过引入另一个包含人员的“store”(好吧,它只是一个美化了的数组)来给它们一些值,并为它们分配任务。
var peopleStore = mobx.observable([
{ name: "Michel" },
{ name: "Me" }
]);
observableTodoStore.todos[0].assignee = peopleStore[0];
observableTodoStore.todos[1].assignee = peopleStore[1];
peopleStore[0].name = "Michel Weststrate";
我们现在有两个独立的store了。一个关于people,一个关于todos。为了将assignee分配给来自people store的人,我们只分配了一个引用。这些更改将由TodoView自动接收。使用MobX时,不需要首先对数据进行规范化,并编写选择器以确保组件将得到更新。事实上,数据存储在哪里并不重要。只要对象是可观察的,MobX就能够跟踪它们。真正的JavaScript引用将正常工作。如果它们与derivation相关,MobX将自动跟踪它们。要进行测试,只需尝试在下一个输入框中更改您的名称(确保您首先按下了上面的Run按钮!)。
顺便说一下,上面输入框的HTML很简单:
由于我们的小Todo应用程序中的所有内容都是从state派生出来的,所以当state发生更改时真的没有关系。这使得创建异步操作变得非常简单。只需按下下面的按钮(多次)来模拟异步加载新的todo条目:
后面的代码非常简单。我们从更新store的属性pendingRequests开始,让UI反映当前的加载状态。加载完成后,我们将存储的todos进行更新,并再次减少pendingRequests计数器。只需将此代码片段与前面的TodoList定义进行比较,就可以看到如何使用pendingRequests属性。
observableTodoStore.pendingRequests++;
setTimeout(function() {
observableTodoStore.addTodo('Random Todo ' + Math.random());
observableTodoStore.pendingRequests--;
}, 2000);
mobx-react-devtools包提供了您在屏幕右上方找到的devtools,可以在任何MobX+ReactJS应用程序中使用。单击第一个按钮将高亮显示正在重新渲染的每个@observer组件。如果您单击第二个按钮,在预览中的某个组件之后,就会显示该组件的依赖树,以便您可以精确地检查在任何给定时刻它正在观察的数据片段。
这是所有!没有样板。只是一些简单的声明性组件,它们构成了完整的UI。它们完全地、交互式地从state中得到。现在可以开始在自己的应用程序中使用mobx和mobx-react包了。以下是到目前为止你学到的东西的简短总结:
- 使用@observable修饰器或observable(对象或数组)函数使MobX能够跟踪对象。
- @computed装饰器可以用来创建能够自动从state派生其值的函数。
- 使用autorun自动运行依赖于某些可观察state的函数。这对于日志记录、网络请求等非常有用。
- 使用mobx-react包中的@observer修饰符可以让React组件真正具有交互性。他们实现自动和有效的更新。即使在具有大量数据的大型复杂应用程序中使用。
您可以自由地使用上面的可编辑代码块多玩一会儿,以了解MobX对所有更改的反应。例如,您可以向报表函数添加一个日志语句,以查看何时调用它。或者根本不显示report,看看它如何影响TodoList的呈现。或者只有在特定的情况下才能展示……
人们经常使用MobX作为Redux的替代。但是请注意,MobX只是一个用于解决技术问题的库,而不是体系结构或状态容器本身。从这个意义上说,上面的例子是人为设计的,建议使用适当的工程实践,比如在方法中封装逻辑,在存储或控制器中组织逻辑等等。
“MobX,在其他地方也有人提到过,但我忍不住要赞美它。用MobX编写意味着使用controllers/ dispatchers/ actions/ supervisors或另一种形式的数据流管理返回成为一种体系结构,您可以根据应用程序的需要进行模式设计,而不只是默认情况下的除了一个Todo应用程序之外的任何东西都需要。”
感兴趣吗?以下是一些有用的资源:
MobX由mweststrate维护