Mobx React 初学者入门指南

state 状态

UI = fn(state)

上述公式表明,给定相同的 state 状态,fn 总是会生成一致的 UI

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

VirtualDOM = fn(props, state)

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

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

state 改变了之后,它会通知它所有的 observers 观察者。UI 只是其中一个最重要的观察者,通常还会有其他观察者。

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

MobX 核心概念

import { observable } from 'mobx';

let cart = observable({
    itemCount: 0,
    modified: new Date()
});
复制代码

observable 是被观察的 state 状态,它是 reactive 响应式的。

声明了被观察者,接着需要声明 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
objectobservable.object({})
arraysobservable.array([])
mapsobservable.map(value)
primitives, functions, class-instancesobservable.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;
    }
}
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值