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;
    }
}
複製代碼
相關文章
相關標籤/搜索