cp from :
通过《Mobx教程(一)-Mobx简介》我们简单理解了Mobx的基本原理及流程,使用Mobx实现一个响应式的应用,主要分为三个步骤:
定义状态并使其可观察(state)
对状态改变的响应(computed、autorun、reaction、observer、when)改变状态(action)下面从这三个步骤的顺序介绍Mobx的主要概念。(版本为5.X、使用es7装饰器)1、可观察状态
状态是驱动应用的数据,是应用的核心。状态可以是与应用相关的各种数据,通常有像待办事项列表这样的领域特定状态,还有像当前已选元素的视图状态,当然,你也可以向Redux类似,将state分为三类:与领域直接相关的领域状态数据,反应用户行为的应用状态数据、视图相关的UI状态数据。使用Mobx一般还会创建一个或多个store用来管理state。
Mobx提供了observable和@observable两个API用来定义可观察的state。
observable(value)@observable classProperty = value
注:下文提到的会触发更新都是指受其影响的computed value(被依赖)和reaction都会自动更新。
(1)JS基本数据类型
JavaScript 中的所有原始类型值都是不可变的,因此值并不会是可观察的,Mobx会将包含值的属性转换成可观察的。可以如下定义:
@observable count = 1;@observable city = 'shanghai';
(2)Array
如果 value 是数组,会返回一个 Observable Array。
@observable list = ['a', 'b', 'c', 'd'];
@observable todos = [{ 'id': 1, 'taskName': 'task1', 'finished': true},{ 'id': 2, 'taskName': 'task2', 'finished': false},{ 'id': 3, 'taskName': 'task3', 'finished': true},{ 'id': 4, 'taskName': 'task4', 'finished': false},{ 'id': 5, 'taskName': 'task5', 'finished': true},{ 'id': 6, 'taskName': 'task6', 'finished': false},];
(3)普通对象
对于没有原型或原型是Object.prototype的普通对象,那么对象会被克隆并且所有的属性都会被转换成可观察的。
{'id': 1, 'taskName': 'task1', 'finished': true}
如果属性的值是一个普通对象,会继续被observable处理,将其属性转换为可观察的,一直如此递归执行。
'id': 1, 'taskName': 'task1', 'finished': {'part1': true, 'part2':false}}
part1和part2也会是可观察的,其值的修改依然会触发更新。如果以后给可观察属性再赋值是一个普通对象时,新的普通对象值的属性也将被转换为可观察的。
若一个可观察对象的值为:
{'id': 1, 'taskName': 'task1', 'finished': true}
{'id': 1, 'taskName': 'task1', 'finished': {'part1': true, 'part2':false}}
对于5.x版本,以后新增加的属性也会是可观察的,4.x及以下版本是不可观察的。
若一个可对象的值为:
{'id': 1, 'taskName': 'task1', 'finished': true}
{'id': 1, 'taskName': 'task1', 'finished': true, owner: admin}
我测试中发现删除对象的属性是不会触发更新的。
(4)Map
对于ES6的Map : 会返回一个新的 Observable Map。Map对象的每一个元素都是可观察的,并且向Map对象中添加和删除新的元素也是可观察的。
@observable task = new Map([['taskName', 'tank1'],['finished', true]]);
this.task.set('owner', 'admin');
(5)非普通对象
非普通对象是指构造函数为自定义函数的对象,非普通对象的属性不会受observable的影响而转换为可观察的,如果需要将非普通对象的属性转换为可观察的,需要在其自定义的构造函数中进行处理。例如:
class User{@observable name;@observable role;constructor(name, role){this.name = name;this.role = role;}}user = new User('admin', '管理员');
之后在对user实例的name或role进行修改,是会触发更新的。
注:对js基本数据类型和非普通对象,observable返回的其实是一个特殊的boxed values类型的可观察对象,保存的是一个指向原基本数据类型或对象的引用,这个引用是可观察的。
2、对状态改变的响应
本段介绍的是Mobx中的衍生(derivation),derivation是指可以从state中衍生出来的任何东西,可以是值(computed value)或者动作(autorun、reaction等)。Derivation可以自动响应state的变化而进行更新或执行有副作用的函数。
(1)Computed
计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值,应该由纯函数产生,计算值可以被其它 observer 使用,例如被ui使用。
Mobx提供了computed和@computed用于定义计算值:
attr = computed(() => value);@computed get attr(){return value;}
@observable list = ['a', 'b', 'c', 'd'];@computed get listLength(){ return this.list.length;}
当你想创建一个永远不会被观察的响应式函数时,可以使用autorun。
disposer = autorun(() => {sideEffect});
当autorun依赖的状态变化时会自动执行参数的function,返回值disposer是autorun的清除函数,当不再需要autorun时,可以调用disposer清除autorun。autorun参数中的函数在使用autorun时会立即执行一次,然后在每次它依赖的state发生变化时自动执行一次。
用法举例:
disposer = autorun(() => { console.log(`autorun : Now the active ID is ${ this.activeItem }`);});
reaction是autorun的变种,第一个参数为数据函数,第二个参数为效果函数,数据函数返回一个值,作为效果函数的参数,效果函数不会对依赖的状态作出反应,只有数据函数返回的值变化时才会执行。
reaction = reaction(() => data, (data, reaction) => { sideEffect })
用法举例:@observable todos = [{ 'id': 1, 'taskName': 'task1', 'finished': true},{ 'id': 2, 'taskName': 'task2', 'finished': false},{ 'id': 3, 'taskName': 'task3', 'finished': true},{ 'id': 4, 'taskName': 'task4', 'finished': false},{ 'id': 5, 'taskName': 'task5', 'finished': true},{ 'id': 6, 'taskName': 'task6', 'finished': false},];reaction = reaction(() => this.todos.filter(item => item.finished === false), notFinished => {if(notFinished.length === 0){ console.log('All tasks have been completed, and autorun and reaction are no longer executed.'); this.disposer(); this.reaction();}else { console.log(`reaction : Now the active ID is ${ this.activeItem}`);}});
在上面的例子中,reaction的第一个参数会为第二个参数提供notFinished,然后第二个参数是效果函数,会执行打印日志的操作,当todo都已经完成时,会调用disposer和reaction清除auto和reaction。
reaction 返回一个清理函数,不需要再执行时应该调用清理函数
数据函数对state的改变作出反应效果函数仅对数据函数中访问的数据作出反应,不对state的改变作出反应autorun第一个参数是函数,可接受第二个参数,处理delay、name、error等(4)observer(其实也是autorun)observer 函数/装饰器可以用来将 React 组件转变成响应式组件,observer 是由单独的 mobx-react 包提供。
mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。
@observer class MyComponent extends React.Component{...}
用法举例:import React, {Component} from 'react';import {inject, observer} from 'mobx-react';import {Button} from 'antd';import Timer from '../Timer';import './style.css'; @inject( 'userStore')@observerexport default class User extends Component{ constructor(props){ super(props); this.state = {}; } render(){ const {user} = this.props.userStore; return(); }}name:{user.name}role:{user.name}{user.isGuest ? `isGuest:${user.isGuest}` : ''}
when(() => condition, () => {sideEffect})
when会自动响应它使用state的变化,也就是会观察并运行第一个参数,直到condition返回true。 一旦返回 true,给定的sideEffect就会被执行,且只会执行一次,然后 autorunner(自动运行程序) 会被清理。 该函数返回一个清理器以提前取消自动运行程序。用法举例:
constructor(){ when(() => this.hasNotFinished.length === 0, () => { this.disposer(); this.reaction(); });}@computed get hasNotFinished(){ return this.todos.filter(item => item.finished === false);}
MobX 会对在追踪函数执行过程中读取现存的可观察属性做出反应。
“读取” 是对象属性的间接引用,可以用过 . (例如 user.name) 或者 [] (例如 user['name']) 的形式完成。
“追踪函数” 是 computed 表达式、observer 组件的 render() 方法和 when、reaction 和 autorun 的第一个入参函数。“过程(during)” 意味着只追踪那些在函数执行时被读取的 observable 。这些值是否由追踪函数直接或间接使用并不重要。MobX 追踪属性访问,而不是值。Mobx不会对可观察状态的改变作出反应举例:
@observable message = { title: "Foo", author: { name: "Michel" }, likes: [ "John", "Sara" ]}
在追踪函数外进行间接引用
ar title = message.title;autorun(() => { console.log(title)})message.title = "Bar"
autorun(() => { setTimeout( () => console.log(message.likes.join(", ")), 10 )})message.likes.push("Jennifer");
MobX 只会为数据是直接通过 render 存取的 observer 组件进行数据追踪
React中使用mobx,只会对render中直接存取的可观察状态进行跟踪像上述将值传递给子组件和将可观察状态值缓存到组件内是不会对状态的变化有反应的。
3、改变状态
动作是任何用来修改状态的函数,且应该永远只对修改状态的函数使用动作。
在mobx中可以在任意位置修改状态,但是推荐使严格模式,在严格模式下,只能在action中修改状态。
Mobx提供action和@action包装action函数
Mobx提供action.bound和@action.bound帮助bind this
@action classMethod() {}
@action(name) classMethod () {}@action(name) boundClassMethod = (args) => { body }@action.bound classMethod() {}用法举例:class commonStoreClass { @observable time = ''; @action updateTime(time){ this.time = time; } @action.bound computedTime(){ const nowTime=new Date(); const year=nowTime.getFullYear(); const month=nowTime.getMonth()+1; const date=nowTime.getDate(); const time = year+"年"+month+"月"+date+" "+nowTime.toLocaleTimeString(); this.updateTime(time); } @action startTime(){ this.timer = setInterval(this.computedTime,1000); } @action stopTime(){ clearInterval(this.timer); }}
上面提到的api中与React相关的由mobx-react提供,其余的由mobx包提供。
import {observable, action, computed, autorun, reaction, when} from 'mobx';import {Provider, inject, observer} from 'mobx-react';