javascript库之——Mobx
接触了Vue之后,才了解了Mobx的意义,所以重新翻译此文,愿一同上路
原文地址,略有删节。
MobX是一个简单、可扩展、久经沙场的状态管理解决方案。这篇教程将会花十分钟指导你所有关于MobX的重要概念。MobX是一个独立的库,但大多数人还是将它和React配合使用,这篇教程也是会关注在两者的结合上。
核心概念
状态管理是每个应用的核心,不一致的状态数据或者与本地变量不同步的情况会很快导致充满bug且无法管理的应用。因此许多状态管理方案都尝试限制修改状态数据的方式,比如让状态数据不可变。但这样引入一些新问题,数据需要规范化,引用完整性也得不到保证,使用prototype这些强大功能也几乎变得不可能。
MobX从根本上让状态管理变得简单:它不会再次建出一个不一致状态。方法也很简单:确保每个源自于应用状态的内容能被自动感知到。
从概念上来说,MobX对待你的应用像一个电子表格。
-
首先,存在一个应用级别的状态。对象图表、数组、原始值、引用组成了应用的模型数据。这些值是应用的数据单元
-
然后就有了派生(derivation)。基本上,任何值都能从应用状态里自动计算获得。这些派生,或者说是计算属性,可以是简单值,比如未完成事项的数量,也可以是像表现未完成事项的HTML展示这种复杂的内容。在电子表格里,这些就是应用的公式和图表。
-
响应(reaction)跟派生很像。主要区别在于这些方法不会生产出值。相反,它们会自动运行一些任务。通常这些都是I/O相关的。它们保证DOM能被更新或者网络请求在合适的时间发出来。
-
最后是动作(action)。动作用来改变状态。MobX保证改变应用状态的动作能被所有派生和响应自动处理,同步并且不会出问题。
简单的todo应用
理论到此为止,实干兴邦。我们创建一个简单的ToDo应用。
1 | class TodoStore { |
我们用todo集合创建了一个todoStore实例。现在是往里面填充一些内容的时刻了。为保证看到变化,我们在每次变化后用todoStore.report打印日志。注意到每次只会打印出第一条。这个例子有点特意人为化,但是很好展示了MobX的依赖追踪是动态的。
1 | todoStore.addTodo("read MobX tutorial"); |
变成响应式
目前这些代码还没有什么特别的。但如果我们不是非得是以显式的方式,而是可以在每次状态数据变化后自动调用report方法岂不是更妙?这样的话将会把我们解放出来。我们希望保证最新的数据被日志打印,但又怕麻烦去组织这行为。
幸好有MobX来帮我们。根据状态数据可以自动地执行代码。然后我们的report方法里的打印自动更新就像电子表格一样。为实现这个目标,TodoStore需要变成可观察的,使得MobX可以追踪变化。让我们修改一下代码来实现它。
另外,completedTodosCount属性能自动的从todo列表里衍生计算出来。通过使用@observable和@computed修饰器我们可以引入可观察属性
1 | class ObservableTodoStore { |
就是这样!我们让一些属性通过@observable装饰器变得可观察的了,并能主动告知MobX这些变化。计算属性通过@computed装饰器来自动从状态数据中派生。
目前还没有使用pendingRequests和assignee属性。为简化例子,我们使用了ES6,JSX和装饰器。不用担心,所有的装饰器在MobX里也有对应的ES5的实现。
在构造方法里我们创建了一个小方法用来打印report并用autorun包裹它。Autorun创建一个只运行一次的响应(reaction),但是能在每次可观察数据变化后自动运行。因为report方法使用到了可观察的todos属性,它将会在合适的时机打印数据。下面的代码显示了这个特性。
1 | observableTodoStore.addTodo("read MobX tutorial"); |
很有趣,对吧?日志被自动打印,同时也没有遗漏中间过程的数据。如果你仔细研究日志,你会发现第四行代码(译者注: observableTodoStore.todos[0].task = “grok MobX tutorial”;,从0计数的)并没有触发一条新的日志,因为report实际上没有被触发调用,尽管背后的数据的确是被替换了。另一个方面,修改第一条todo的name属性会触发report,因为name属性在report里被使用到了。这个例子很好体现了autorun不仅监测了todos数组对象,还监测了其中的特定的字段。
让React也变成响应式
现下我们仅是做了一个很傻很天真的响应式日志。是时候表演真正的技术了!来做一个界面展示出来。React的组件并不是开箱即为响应式的。mobx-react的@observer修饰器能让React组件的render方法放入autorun中,自动在组件和状态数据之间同步。在概念上和刚才的report没有什么区别。
下面的代码定义了一些React组件。跟MobX有关的其实只有@observer装饰器。但保证每个组件各自在相关数据变化时重新渲染已是足够了。不再需要调用setState,也不要搞明白如何用selector订阅或者注入高阶组件之类的东西。几乎所有的组件都变得聪明起来。尽管它们以一种傻傻的方式来被定义。
1 | @observer |
下面的代码很好的显示了仅仅只要改变数据而不用做任何别的事情。MobX会自动计算衍生数据,用状态数据更新相关的用户界面。
1 | const store = observableTodoStore; |
引用
目前我们创建出了可观察的对象,数组和基础类型。你可能奇怪,MobX里引用是什么样子的?我的状态数据结构能是一种图的形式吗?之前你可能意识到todos数组上有assignee字段。我们引入另一个store包含被赋予任务的人。
1 | var peopleStore = mobx.observable([ |
现在有两个独立的store了。一个是people一个是todos。给assignee赋值的时候我们仅仅是用引用。但变化也能被自动更新到TodoView上。有了Mobx,就不需要把数据先范式化,再写selector来保证视图得到更新。实际上数据在哪里存储也根本不重要。只要对象是可观察的,MobX将会跟踪它们。JavaScript的引用也能被跟踪。MobX也将会自动跟踪衍生计算出来的数值。
异步操作
由于Todo应用里所有都是从状态数据里衍生出来的,所以状态什么时候更改并不重要。
略去按钮操作部分
下面的代码很直观,我们用pendingRequests来让界面显示当前的加载状态。一旦加载完成,更新todos并且减去pendingRequests。和之前的TodoList代码比较一下,看看pendingRequests是如何使用的。
1 | observableTodoStore.pendingRequests++; |
开发工具
以下工具可以被用来分析你的mobx-react应用
-
组件的重复渲染可视化是React devtools的一部分了
-
React devtools也显示组件依赖树关系
-
事件仍然可以通过mobx-logger或者手工使用mobx的spy或trace方法用浏览器插件查看
mobx-react-devtools不再被支持了。
总结
这就是全部!没有标杆代码。只有一些简单的组件组成了我们全部的界面,所有的一切都从状态数据里衍生出来。你现在可以在应用里使用Mobx和mobx-react了。简单的总结如下
-
使用@observable装饰器或者observable(object / array)方法来使得Mobx对象可追踪
-
@computed装饰器可以被用来修饰从状态数据衍生出来的创建方法
-
使用autorun来根据可观察状态自动运行方法。对于日志,请求网络等行为很有用
-
使用mobx-react的@observer装饰器来让React组件也变响应式。它们能自动有效的更新,即便是在大型复杂的应用中使用大量的数据
MobX不是一个状态容器
人们经常把MobX当做是Redux的替代。但要注意到MobX仅是一个解决技术难题的库,而并不是一种状态容器的架构。从这个意义上来说,上面的例子太做作了,推荐使用合适的练习方式,比如在方法里封装逻辑,在store或controller里组织数据。