十分钟介绍mobx与react
原文地址:https://mobxjs.github.io/mobx/getting-started.html
写在前面:本人英语水平有限,主要是写给自己看的,若有哪位同学看到了有问题的地方,请为我指出,非常感谢;
mobx是一个比redux更好的状态管理包,代码量更少,思路更清晰,没有像redux那样复杂的reducer,action (ps: redux的作者也推荐mobx,某大大告诉我的,并没有原话链接)
1.mobx 反应流程
2.the core idea
State 是每一个应用程序的核心部分,而使用一个不合规范的 State 则是让你的应用充满 bug 和失控的不二法门,或者就是局部变量环绕,让你的 state 失去了同步。有很多框架试图解决这个问题,比如使用不可变的 state,但是这样以来又带来了新的问题,比如数据必须规格化,完整性约束失效等等。并且它变得不可能使用强大的概念,如原型。
MobX 让整个事情又变简单了:它不允许产生失控的 state。它的理念也很简单:所有可以从 state 中派生的事物,都会自动的派生。
从概念上讲,Mobx会像Excel 表格一样处理您的应用程序。
- 首先,有一个 state,它可以是一个object,array,primitives等等任何组成你程序的部分。你可以把这个想象成你应用程序的“单元格”。
- 然后就是 derivations,一般它是指可以从 state 中直接计算的来的结果。比如未完成的任务的数量,这个比较简单,也可以稍复杂一些比如渲染你的任务显示的html。它类似于你的应用程序中的“公式和图表”。
- Reactions 和 derivations 很像,主要的区别在于 reactions 并不产生数据结果,而是自动完成一些任务,一般是和 I/O 相关的。他们保证了 DOM 和 网络请求会自动适时地出发。
- 最后是 actions。Actions 指的是所有会改变 state 的事情,MobX 保证所有 actions 都会有对应的 derivations 和 reactions 相伴,保证同步。
2.A simple todo store...
理论说的够多的了,看一个例子也许会更明白一些。我们从一个简单的 todo 程序开始。
为了原创性,让我们从一个非常简单的ToDoStore开始。下面是一个非常直接的TodoStore,它维护着一个todos的集合。没有MobX参与。
class TodoStore { todos = []; get completedTodosCount() { return this.todos.filter( todo => todo.completed === true ).length; } report() { if (this.todos.length === 0) return "<none>"; 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.report并记录它。
请注意,报表有意总是只打印第一个任务。它使这个例子有点人工,但正如你将看到下面它很好地演示了MobX的依赖关系跟踪是动态的。
===》
3.Becoming reactive
到目前为止,这段代码没有什么特别之处。但是如果我们可以不再手动调用 report 方法,只声明我们希望在每次状态更改时调用它?我们只需要在想要的地方修改这个 state,所有的汇报都自动来做。
幸运的是,这正是MobX可以为你做的。自动执行完全取决于状态的代码。这样我们的报表功能就会自动更新,就像电子表格中的图表一样。为了实现这一点,TodoStore必须成为可观察的(observable),以便MobX可以跟踪所有正在进行的更改。让我们改变类就足以实现它。
同时,completedTodosCount 属性应该被自动派生。通过使用@observable和@computed装饰器,我们可以在对象上引入observable属性:
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 "<none>"; 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来装饰,以识别这些可以从状态导出。
pendingRequests和assignee属性到目前为止未使用,但将在本教程后面使用。为了简洁,本页上的所有示例都使用ES6,JSX和装饰器。但不要担心,所有的装饰在MobX有一个ES5写法。
在构造函数中,我们创建了一个report()将其包装在mobx.autorun()。mobx.autorun()创建一个运行一次的reaction,然后每当在函数内部使用的任何可观察数据发生变化时,自动重新运行。因为report()用observable todos属性,所以它会在适当时打印报表。这在下一个列表中演示。只需按下运行按钮:
==>
纯函数,对吧?report自动打印了,同步并且没有泄漏的中间值。如果你仔细查看运行结果的话,你会发现我们的第四句语句没有产生输出,因为我们修改了todos[1]的数据,而我们在report中指明的数据,并没有todos[1]的变化而发生变化。而第五句话修改了todos[0]的数据则输出了。这个例子很好的说明了,autorun不是简单的监视了todos,而是精确到了具体的一项。
4.Making React reactive
Ok, so far we made a silly report reactive. Time to build a reactive user interface around this very same store. React components are (despite their name) not reactive out of the box. The @observer
decorator from the mobx-react
package fixes that by wrapping the React component render
method in autorun
, automatically keeping your components in sync with the state. That is conceptually not different from what we did with the report
before.
好了,到目前为止我们做了一个愚蠢的 report reactive。是时候用非常相似的store创建一个reactive了。React components(尽管他们的名字)没有反应开箱。
mobx-react包中的@observer装饰器通过在mobx.autorun()包装React组件的render()方法来实现,自动保持组件与状态同步。这在概念上与我们以前的报告没有什么不同
下面的清单定义了几个React组件。在那里唯一属于MobX的东西是@observer装饰器。这足以确保每个组件在相关数据更改时单独重新render。您不需要再调用setState,也不必了解如何使用需要配置的选择器或更高级的组件来(subscribe :redux中有 )订阅应用程序状态的适当部分。基本上,所有组件都变得聪明。然而,它们是以愚蠢的,声明性的方式定义的。
@observer class TodoList extends React.Component { render() { const store = this.props.store; return ( <div> { store.report } <ul> { store.todos.map( (todo, idx) => <TodoView todo={ todo } key={ idx } /> ) } </ul> { store.pendingRequests > 0 ? <marquee>Loading...</marquee> : null } <button onClick={ this.onNewTodo }>New Todo</button> <small> (double-click a todo to edit)</small> <RenderCounter /> </div> ); } 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 ( <li onDoubleClick={ this.onRename }> <input type='checkbox' checked={ todo.completed } onChange={ this.onToggleCompleted } /> { todo.task } { todo.assignee ? <small>{ todo.assignee.name }</small> : null } <RenderCounter /> </li> ); } 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( <TodoList store={ observableTodoStore } />, document.getElementById('reactjs-app') );
下一个清单很好地显示,我们只需要改变我们的数据,而不做任何其他事情。 MobX将自动从存储中的状态重新导出和更新用户界面的相关部分。
运行结果:
5.Working with references
到目前为止,我们已经创建了observable对象(原型对象和plain对象),数组和基元(primitives)。你可能想知道,在MobX中如何处理引用?我的state是否允许形成图表?在上一个列表中,您可能已经注意到todos有一个assignee
属性。让我们给他们一些值,通过引入另一个“store”(它只是一个glorified数组)包含人,和分配给他们的任务。
那么问题来了,observableTodoStore.todos本来就是@observable的,奈何有要再增加一个呢?
答:大概因为改变的时候方便?
We now have two independent stores. One with people and one with todos. To assign an assignee
to a person from the people store, we just assigned a reference. These changes will be picked up automatically by the TodoView
. With MobX there is no need to normalize data first and to write selectors to make sure our components will be updated. In fact, it doesn't even matter where the data is stored. As long as objects are made observable, MobX will be able to track them. Real JavaScript references will just work. MobX will track them automatically if they are relevant for a derivation. To test that, just try changing your name in the next input box (make sure you have pressed the above Run code button first!).
我们现在有两个独立的商店。一个人和一个todos。要从people store中分配一个person的代理,我们只需要分配了一个参考。这些更改将由TodoView自动选取。使用MobX,没有必要首先规范化数据,并且编写选择器以确保我们的组件将被更新。事实上,数据存储的位置甚至都不重要。只要对象是可观察的,MobX将能够跟踪它们。真正的JavaScript引用将正常工作。 MobX将自动跟踪它们,如果它们与派生相关。要测试,只需尝试在下一个输入框中更改您的名称(确保您已经按上面的Run代码按钮!)。
6.Asynchronous actions
因为我们的小Todo应用程序中的一切都是从状态派生而来的,所以当状态改变时它并不重要。这使得创建异步操作真的很简单。只需按下面的按钮(多次)来模拟异步加载新的todo项:
后面的代码真的很简单。我们从更新存储属性pendingRequests开始,让UI反映当前的加载状态。一旦加载完成,我们更新商店的todos并再次减少pendingRequests计数器。只需将此代码段与早期的TodoList定义进行比较,即可了解如何使用pendingRequests属性
7.Conclusion
就这样!没有样板。只是一些UI组件中的简单的声明性组件,形成我们的完整的UI。这些是完全地,反应性地从我们的state中得到。现在,您可以开始在自己的应用程序中使用mobx和mobx-react包。您到目前为止学到的东西的简短摘要:
- 使用@observable装饰器或observable(obj or array)函数令对象可以被MobX追踪。
- @computed装饰器可以用于创建可以从状态自动导出其值的函数。
- 使用autorun运行依赖于某个可观察状态的函数。这对log,网络请求等很有用.
- 使用来自mobx-react包的@observer装饰器,使您的React组件真正被动。他们将自动和有效地更新。即使用于具有大量数据的大型复杂应用程序。
(我这里是不行的)
可以随意使用上面的可编辑代码块玩一段时间,以获得MobX对所有更改的反应的基本感觉。例如,您可以向报告函数添加一条日志语句,以查看它被调用的时间。或者根本不显示报告,看看它如何影响TodoList的呈现。或仅在特定情况下显示...
8.MobX is not a state container
人们经常使用MobX作为Redux的替代品。但请注意,MobX只是一个库来解决技术问题,而不是一个架构或甚至状态容器本身。在这个意义上,上面的例子是设计的,并且建议使用适当的工程实践,如在方法中封装逻辑,在商店或控制器等组织它们。或者,正如HackerNews上的某人所说:
“MobX,它在其他地方被提到,但我不能不赞扬它。在MobX中编写意味着使用控制器/调度程序/操作/管理程序或另一种形式的管理数据流返回到一个架构问题,您可以模式化您的应用程序的需要,而不是默认情况下需要的任何东西比一个Todo应用程序。