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;
}
}
複製代碼