Mobx React 初学者入门指南

state 状态

UI = fn(state)css

上述公式代表,给定相同的 state 状态,fn 老是会生成一致的 UIreact

在 React 的世界里,还须要加上 props 才完整:es6

VirtualDOM = fn(props, state)npm

Action => state => UI

从图中咱们能够看出,在 UI 上,咱们能够进行界面操做(点按钮,敲键盘输入等),这些界面操做被称为 action 。这里很重要的一点是,数据流向是 UI => action => stateUI 不直接修改 state ,而是经过派发 action 从而改变 stateredux

这样作的好处显而易见,UI 层负责的就仅是同步的界面渲染。bash

state 改变了以后,它会通知它全部的 observers 观察者。UI 只是其中一个最重要的观察者,一般还会有其余观察者。app

side effect

另外的观察者被通知咱们称之为 side effects,执行完 side effect 以后,它自身会再进行 action 的派发去更新 state ,这和 state 有本质上的区别。async

MobX 核心概念

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 反作用也叫作 reactionsreactionaction 的区别在于:action 是用于改变 state 的,而 reaction 则是 state 改变后须要去执行的。

action => state => reaction
动做改变状态,状态引发反应。

Observables , Actions , Reactions

observable() 会将 objectarraymap 转化成 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种类型的 reactionsautorun(), reaction(), when()

autorun()

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-functiondisposer-function 函数,此返回函数用于中止 autorun() 的监听,相似于clearTimer(timer) 的做用。

reaction()

reaction(tracker-function, effect-function): disposer-function
tracker-function: () => data, effect-function: (data) => {}

reaction()autorun() 多出一个 tracker-function 函数,这个函数用于根据监听的 state 生成输出给 effect-functiondata 。只有当这个 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()

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) 复制代码

MobX React

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')
);
复制代码

而后在其余组件文件里经过 injectstore 注入到 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; 复制代码

store 设计

恭喜你看到了初学者指南的最后一个章节,本文并无涉及到不少 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;
    }
}
复制代码
相关文章
相关标签/搜索