UI = fn(state)
css
上述公式代表,给定相同的 state
状态,fn
老是会生成一致的 UI
。react
在 React 的世界里,还须要加上 props
才完整:es6
VirtualDOM = fn(props, state)
npm
从图中咱们能够看出,在 UI 上,咱们能够进行界面操做(点按钮,敲键盘输入等),这些界面操做被称为 action
。这里很重要的一点是,数据流向是 UI => action => state
,UI
不直接修改 state
,而是经过派发 action
从而改变 state
。redux
这样作的好处显而易见,UI
层负责的就仅是同步的界面渲染。bash
当 state
改变了以后,它会通知它全部的 observers
观察者。UI
只是其中一个最重要的观察者,一般还会有其余观察者。app
另外的观察者被通知咱们称之为 side effects
,执行完 side effect
以后,它自身会再进行 action
的派发去更新 state
,这和 state
有本质上的区别。async
import { observable } from 'mobx';
let cart = observable({
itemCount: 0,
modified: new Date()
});
复制代码
observable
是被观察的 state 状态,它是 reactive 响应式的。ide
声明了被观察者,接着须要声明 observer
观察者才有意义。函数
import { observable, autorun } from 'mobx';
autorun(() => {
console.log(`The Cart contains ${cart.itemCount} item(s).`);
}); // => 控制台输出: The Cart containers 0 item(s)
cart.itemCount++; // => 控制台输出: The Cart containers 1 item(s)
复制代码
autorun
是其中一种观察者,它会自动观察函数里的 observable
变量,若是函数里的变量发生了改变,它就会执行函数一遍。(比较特殊的是,它会在注册函数以后立刻执行一遍而无论变量有没有改变。因此才有了上面 itemCount
改变了一次而 autorun
执行2次的结果)
相似于 redux
的思想,直接修改 state
是罪恶的,而且最终致使程序的混乱。在 mobx
里面也如此,上面的 cart.itemCount++
这个操做,咱们须要把它放到 action
中去。
import { observable, autorun, action } from 'mobx';
const incrementCount = action(() => {
cart.itemCount++;
})
incrementCount();
复制代码
在 mobx
里, side effects
反作用也叫作 reactions
。reaction
和 action
的区别在于:action
是用于改变 state
的,而 reaction
则是 state
改变后须要去执行的。
action => state => reaction
动做改变状态,状态引发反应。
observable()
会将 object
,array
或 map
转化成 observable entity 被观察的实体。而对于 JavaScript 的基本类型(number
, string
, boolean
, null
, undefined
),function
函数或者 class
类类型,则不会起做用,甚至会抛出异常。
对于这些特殊类型,MobX 提供了 observable.box()
API,用法以下:
const count = observable.box(20);
console.log(`Count is ${count.get()}`); // get()
count.set(25); // set()
复制代码
对于 observable
的具体使用 API 场景以下:
数据类型 | API |
---|---|
object | observable.object({}) |
arrays | observable.array([]) |
maps | observable.map(value) |
primitives, functions, class-instances | observable.box(value) |
MobX 还有相似 Vuex 的 computed
的功能,在 MobX 咱们管它叫 derivations
派生状态。使用它很简单,只须要在对象上声明 get
属性:
const cart = observable.object({
items: [],
modified: new Date(),
get description() {
switch (this.items.length) {
case 0:
return 'no items in the cart';
default:
return `${this.items.length} items in the cart`;
}
}
})
复制代码
上面咱们都是在使用 es5 语法,在 es6 里咱们能够用装饰器的形式来使用咱们的 MobX:
class Cart {
@observable.shallow items = []; // => observable.array([], { deep: false })
@observable modified = new Date();
@computed get description() {
switch (this.items.length) {
case 0:
return 'no items in the cart';
default:
return `${this.items.length} items in the cart`;
}
}
@action
addItem = () => {
this.items.push('new one');
}
}
复制代码
MobX 有3种类型的 reactions
:autorun()
, reaction()
, when()
。
import { observable, action, autorun } from 'mobx';
class Cart {
@observable modified = new Date();
@observable.shallow items = [];
constructor() {
this.cancelAutorun = autorun(() => {
console.log(`Items in Cart: ${this.items.length}`); // 1. 控制台输出: Items in Cart: 0
});
}
@action
addItem(name, quantity) {
this.items.push({ name, quantity });
this.modified = new Date();
}
}
const cart = new Cart();
cart.addItem('Power Cable', 1); // 2. 控制台输出: Items in Cart: 1
cart.addItem('Shoes', 1); // 3. 控制台输出: Items in Cart: 2
cart.cancelAutorun();
cart.addItem('T Shirt', 1); // 控制台不输出
复制代码
autorun(effect-function): disposer-function
effect-function: (data) => {}
能够从 autorun()
的签名看出,执行 autorun()
以后返回一个可注销 effect-function
的 disposer-function
函数,此返回函数用于中止 autorun()
的监听,相似于clearTimer(timer)
的做用。
reaction(tracker-function, effect-function): disposer-function
tracker-function: () => data, effect-function: (data) => {}
reaction()
比 autorun()
多出一个 tracker-function
函数,这个函数用于根据监听的 state
生成输出给 effect-function
的 data
。只有当这个 data
变化的时候,effect-function
才会被触发执行。
import { observable, action, reaction, toJS } from 'mobx';
class ITOffice {
@observable members = []
constructor() {
reaction(() => {
const femaleMember = this.members.find(x => x.sex === 'female');
return femaleMember;
}, femaleMember => {
console.log('Welcome new Member !!!')
})
}
@action addMember = (member) => {
this.members.push(member)
}
}
const itoffice = new ITOffice();
itoffice.addMember({
name: 'salon lee',
sex: 'male'
});
itoffice.addMember({
name: 'little ming',
sex: 'male'
});
itoffice.addMember({
name: 'lady gaga',
sex: 'female'
}); // 1. 控制台输出: Welcome new Member !!!
复制代码
上面这家办公室,reaction()
监听了新员工的加入,可是只有当新员工的性别是女生的时候,人们才会喊欢迎口号。这种区别对待的控制就是经过 tracker-function
实现的。
when(predicate-function, effect-function): disposer-function
predicate-function: () => boolean, effect-function: () => {}
when()
和 reaction()
相似,都有个前置判断函数,可是 when()
返回的是布尔值 true/false
。只有当 predicate-function
返回 true
时,effect-function
才会执行,而且 effect-function
只会执行一遍。也就是说 when()
是一次性反作用,当条件为真致使发生了一次反作用以后,when()
便自动失效了,至关于本身调用了 disposer-function
函数。
when()
还有另一种写法,就是使用 await when()
而且只传第一个 predicate-function
参数。
async () {
await when(predicate-function); effect-function(); } // <= when(predicate-function, effect-function) 复制代码
在 React
里使用 mobx
,咱们须要安装 mobx-react
库。
npm install mobx-react --save
复制代码
而且使用 observer
链接 react 组件和 mobx 状态。
首先建立咱们的购物车:
// CartStore.js
import { observer } from "mobx-react";
export default class Cart {
@observer modified = new Date();
@observer.shallow items = [];
@action
addItem = (name, quantity) {
while (quantity > 0) {
this.items.push(name)
quantity--;
}
this.modified = new Date();
}
}
复制代码
而后将购物车状态经过 Provider
注入到上下文当中:
// index.js
import { Provider } from 'mobx-react';
import store from './CartStore'
ReactDOM.render(
<Provider store={new store()}> <App /> </Provider>,
document.getElementById('root')
);
复制代码
而后在其余组件文件里经过 inject
将 store
注入到 props
:
// app.js
import React from 'react';
import './App.css';
import { inject, observer } from 'mobx-react';
@inject('store')
@observer
class App extends React.Component {
render() {
const { store } = this.props;
return (
<React.Fragment> {store.items && store.items.map((item, idx) => { return <p key={item + idx}>{item + idx}</p> })} <button onClick={() => store.addItem('shoes', 2)}>添加2双鞋子</button> <button onClick={() => store.addItem('tshirt', 1)}>添加1件衬衫</button> </React.Fragment> ); } } export default App; 复制代码
恭喜你看到了初学者指南的最后一个章节,本文并无涉及到不少 MobX
的高级 API 和内层原理,是由于.. 标题叫 “初学者指南” 啊咱们干吗要拿这些那么难的东西出来吓唬人,并且你认证看完上面的内容后,绝对能应付平时绝大多数开发场景了。因此不要慌,看到这里你也算是入门 mobx
了。恭喜恭喜。
最后这里展现的是当你使用 mobx
做为你的状态管理方案的时候,你应该如何设计你的 store
。其实这更偏向于我的或团队风格,和利弊双面性层面上的思考。
这里并无标准答案,仅供参考。
state
class Hero {
@observable name = 'Hero'; // 名字
@observable blood = 100; // 血量
@observable magic = 80; // 魔法值
@observable level = 1; // 等级
constructor(name) {
this.name = name; // 初始化英雄名字
}
}
复制代码
state
衍生出 computed
class Hero {
@observable name = 'Hero';
@observable blood = 100;
@observable magic = 80;
@observable level = 1;
@computed
get isLowHP() { // 是否低血量
return this.blood < 25;
}
@computed
get isLowMC() { // 是否低魔法值
return this.magic < 10;
}
@computed
get fightLevel() { // 战斗力
return this.blood * 0.8 + this.magic * 0.2 / this.level
}
constructor(name) {
this.name = name;
}
}
复制代码
action
class Hero {
@observable name = 'Hero';
@observable blood = 100;
@observable magic = 80;
@observable level = 1;
@computed
get isLowHP() {
return this.blood < 25;
}
@computed
get isLowMC() {
return this.magic < 10;
}
@computed
get fightLevel() {
return this.blood * 0.8 + this.magic * 0.2 / this.level
}
@action.bound
beAttack(num) { // 被攻击
this.blood -= num;
}
@action.bound
releaseMagic(num) { // 释放魔法
this.magic -= num;
}
@action.bound
takePill() { // 吃药丸
this.blood += 50;
this.magic += 25;
}
constructor(name) {
this.name = name;
}
}
复制代码