Mobx | 強大的狀態管理工具 | 能夠用Mobx來替代掉redux

來源簡書 電梯直達 https://www.jianshu.com/p/505d9d9fe36ajavascript

Mobx是一個功能強大,上手很是容易的狀態管理工具。就連redux的做者也曾經向你們推薦過它,在很多狀況下你的確可使用Mobx來替代掉redux。
本教程旨在介紹其用法及概念,並重點介紹其與React的搭配使用。html

先來看看最基本的用法。java

observable和autorun

import { observable, autorun } from 'mobx'; const value = observable(0); const number = observable(100); autorun(() => { console.log(value.get()); }); value.set(1); value.set(2); number.set(101); 

能夠看到,控制檯中依次輸出0,1,2。
observable能夠用來觀測一個數據,這個數據能夠數字、字符串、數組、對象等類型(相關知識點具體會在後文中詳述),而當觀測到的數據發生變化的時候,若是變化的值處在autorun中,那麼autorun就會自動執行。
上例中的autorun函數中,只對value值進行了操做,而並無number值的什麼事兒,因此number.set(101)這步並不會觸發autorun,只有value的變化才觸發了autorun。react

計算屬性——computed

假如如今咱們一個數字,但咱們對它的值不感興趣,而只關心這個數組是否爲正數。這個時候咱們就能夠用到computed這個屬性了。git

const number = observable(10); const plus = computed(() => number.get() > 0); autorun(() => { console.log(plus.get()); }); number.set(-19); number.set(-1); number.set(1); 

依次輸出了true,false,true。
第一個true是number初始化值的時候,10>0爲true沒有問題。
第二個false將number改變爲-19,輸出false,也沒有問題。
可是當-19改變爲-1的時候,雖然number變了,可是number的改變實際上並無改變plus的值,因此沒有其它地方收到通知,所以也就並無輸出任何值。
直到number從新變爲1時才輸出true。github

實際項目中,computed會被普遍使用到。typescript

const price = observable(199); const number = observable(15); //computed的其它簡單例子 const allPrice = computed(() => price.get() * number.get()); 

順便一提,computed屬性和React Native中的ListView搭配使用很愉快。json

action,runInAction和嚴格模式(useStrict)

mobx推薦將修改被觀測變量的行爲放在action中。
來看看如下例子:redux

import {observable, action} from 'mobx'; class Store { @observable number = 0; @action add = () => { this.number++; } } const newStore = new Store(); newStore.add(); 

以上例子使用了ES7的decorator,在實際開發中很是建議用上它,它能夠給你帶來更多的便捷api

好了回到咱們的例子,這個類中有一個add函數,用來將number的值加1,也就是修改了被觀測的變量,根據規範,咱們要在這裏使用action來修飾這個add函數。
敢於動手的你也許會發現,就算我把@action去掉,程序仍是能夠運行呀。

class Store { @observable number = 0; add = () => { this.number++; } } 

這是由於如今咱們使用的Mobx的非嚴格模式,若是在嚴格模式下,就會報錯了。
接下來讓咱們來啓用嚴格模式

import {observable, action, useStrict} from 'mobx'; useStrict(true); class Store { @observable number = 0; @action add = () => { this.number++; } } const newStore = new Store(); newStore.add(); 

嗯,Mobx裏啓用嚴格模式的函數就是useStrict,注意和原生JS的"use strict"不是一個東西。
如今再去掉@action就會報錯了。
實際開發的時候建議開起嚴格模式,這樣不至於讓你在各個地方很輕易地區改變你所須要的值,下降不肯定性。

action的寫法大概有以下幾種(摘自mobx英文文檔):

  • action(fn)
  • action(name, fn)
  • @action classMethod() {}
  • @action(name) classMethod () {}
  • @action boundClassMethod = (args) => { body }
  • @action(name) boundClassMethod = (args) => { body }
  • @action.bound classMethod() {}
  • @action.bound(function() {})

能夠看到,action在修飾函數的同時,咱們還能夠給它設置一個name,這個name應該沒有什麼太大的做用,但能夠做爲一個註釋更好地讓其餘人理解這個action的意圖。

接下來講一個重點action只能影響正在運行的函數,而沒法影響當前函數調用的異步操做
好比官網中給了以下例子

@action createRandomContact() {
  this.pendingRequestCount++; superagent .get('https://randomuser.me/api/') .set('Accept', 'application/json') .end(action("createRandomContact-callback", (error, results) => { if (error) console.error(error); else { const data = JSON.parse(results.text).results[0]; const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture); contact.addTag('random-user'); this.contacts.push(contact); this.pendingRequestCount--; } })); } 

重點關注程序的第六行。在end中觸發的回調函數,被action給包裹了,這就很好驗證了上面加粗的那句話,action沒法影響當前函數調用的異步操做,而這個回調毫無疑問是一個異步操做,因此必須再用一個action來包裹住它,這樣程序纔不會報錯。。

固然若是你說是在非嚴格模式下……那當我沒說吧。。

若是你使用async function來處理業務,那麼咱們可使用runInAction這個API來解決以前的問題。

import {observable, action, useStrict, runInAction} from 'mobx'; useStrict(true); class Store { @observable name = ''; @action load = async () => { const data = await getData(); runInAction(() => { this.name = data.name; }); } } 

你能夠把runInAction有點相似action(fn)()的語法糖,調用後,這個action方法會馬上執行。

結合React使用

在React中,咱們通常會把和頁面相關的數據放到state中,在須要改變這些數據的時候,咱們會去用setState這個方法來進行改變。
先設想一個最簡單的場景,頁面上有個數字0和一個按鈕。點擊按鈕我要讓這個數字增長1,就讓咱們要用Mobx來處理這個試試。

import React from 'react'; import { observable, useStrict, action } from 'mobx'; import { observer } from 'mobx-react'; useStrict(true); class MyState { @observable num = 0; @action addNum = () => { this.num++; }; } const newState = new MyState(); @observer export default class App extends React.Component { render() { return ( <div> <p>{newState.num}</p> <button onClick={newState.addNum}>+1</button> </div> ) } } 

上例中咱們使用了一個MyState類,在這個類中定義了一個被觀測的num變量和一個action函數addNum來改變這個num值。
以後咱們實例化一個對象,叫作newState,以後在個人React組件中,我只須要用@observer修飾一下組件類,即可以愉悅地使用這個newState對象中的值和函數了。

跨組件交互

在不使用其它框架、類庫的狀況下,React要實現跨組件交互這一功能相對有些繁瑣。一般咱們須要在父組件上定義一個state和一個修改該state的函數。而後把state和這個函數分別傳到兩個子組件裏,在邏輯簡單,且子組件不多的時候可能還好,但當業務複雜起來後,這麼寫就很是繁瑣,且難以維護。而用Mobx就能夠很好地解決這個問題。來看看如下的例子:

class MyState {
  @observable num1 = 0;
  @observable num2 = 100;

  @action addNum1 = () => {
    this.num1 ++;
  };
  @action addNum2 = () => {
    this.num2 ++;
  };
  @computed get total() {
    return this.num1 + this.num2;
  }
}

const newState = new MyState();

const AllNum = observer((props) => <div>num1 + num2 = {props.store.total}</div>); const Main = observer((props) => ( <div> <p>num1 = {props.store.num1}</p> <p>num2 = {props.store.num2}</p> <div> <button onClick={props.store.addNum1}>num1 + 1</button> <button onClick={props.store.addNum2}>num2 + 1</button> </div> </div> )); @observer export default class App extends React.Component { render() { return ( <div> <Main store={newState} /> <AllNum store={newState} /> </div> ); } } 

有兩個子組件,Main和AllNum (均採用無狀態函數的方式聲明的組件)
在MyState中存放了這些組件要用到的全部狀態和函數。
以後只要在父組件須要的地方實例化一個MyState對象,須要用到數據的子組件,只須要將這個實例化的對象經過props傳下去就行了。

那若是組件樹比較深怎麼辦呢?
咱們能夠藉助React15版本的新特性context來完成。它能夠將父組件中的值傳遞到任意層級深度的子組件中。
詳情能夠查看React的官方文檔 React context

接下來看看網絡請求的狀況。

useStrict(true); class MyState { @observable data = null; @action initData = async() => { const data = await getData("xxx"); runInAction("說明一下這個action是幹什麼的。不寫也能夠", () => { this.data = data; }) }; } 

嚴格模式下,只能在action中修改數據,可是action只能影響到函數當前狀態下的情景,也就是說在await以後發生的事情,這個action就修飾不到了,因而咱們必需要使用了runInAction(詳細解釋見上文)。
固然若是你不開啓嚴格模式,不寫runInAction也不會報錯。

我的強烈建議開啓嚴格模式,這樣能夠防止數據被任意修改,下降程序的不肯定性

關於@observer的一些說明

一般,在和Mobx數據有關聯的時候,你須要給你的React組件加上@observer,你沒必要太擔憂性能上的問題,加上這個@observer不會對性能產生太大的影響,並且@observer還有一個相似於pure render的功能,甚至能起到性能上的一些優化。

所謂pure render見下例:

@observer
export default class App extends React.Component { state = { a: 0, }; add = () => { this.setState({ a: this.state.a + 1 }); }; render() { return ( <div> {this.state.a} <button onClick={this.add}>+1</button> <PureItem /> </div> ); } } @observer class PureItem extends React.Component { render() { console.log('PureItem的render觸發了'); return ( <div>大家的事情跟我不要緊</div> ); } } 

若是去掉子組件的@observer,按鈕每次點擊,控制檯都會輸出 PureItem的render觸發了 這句話。

React組件中能夠直接添加@observable修飾的變量

@observer
class MyComponent extends React.Component { state = { a: 0 }; @observable b = 1; render() { return( <div> {this.state.a} {this.b} </div> ) } } 

在添加@observer後,你的組件會多一個生命週期componentWillReact。當組件內被observable觀測的數據改變後,就會觸發這個生命週期。
注意setState並不會觸發這個生命週期!state中的數據和observable數據並不算是一類。

另外被observable觀測數據的修改是同步的,不像setState那樣是異步,這點給咱們帶了很大便利

Observable Object和Observable Arrays

本章主要對官方文檔Observable Types這一節中的前兩章進行了翻譯概述。有興趣的同窗能夠直接閱讀官方文章 Mobx官方文檔——Observable Types

Observable Objects

若是使用observable來修飾一個Javascript的簡單對象,那麼其中的全部屬性都將變爲可觀察的,若是其中某個屬性是對象或者數組,那麼這個屬性也將被observable進行觀察,說白了就是遞歸調用。
Tips: 簡單對象是指不禁構造函數建立,而是使用Object做爲其原型,或是乾脆沒有原型的對象。
須要注意,只有對象上已經存在的屬性,才能被observable所觀測到。
如果當時不存在,後續添加的屬性值,則須要使用extendObservable來進行添加。

let observableObject = observable({value: 3222}); extendObservable(observableObject, { newValue: 2333 }); 

若是是由構造函數建立的對象,那麼必需要再它的構造函數中使用observable或extendObservable來觀測對象。
以下所示:

function MyObject(name) { extendObservable(this, { name, }); } var obj = new MyObject("aaa"); 

若是對象中的屬性是由構造函數建立的對象,那麼它也不會被observable給轉化。

對象中帶有getter修飾的屬性會被computed自動轉換。

其實observable函數的自動轉化已經可以解決至少95%的問題了,若是想要更詳細地瞭解,能夠去看 modifiers這一章

最後附一個購物車的例子

Observable Arrays

與對象相似,數組一樣可使用observable函數進行轉化。

考慮到ES5中原生數組對象中存在必定的限制,因此Mobx將會建立一個類數組對象來代替原始數組。在實際使用中,這些類數組的表現和真正的原生數組極其相似,而且它支持原生數組的全部API,包括數組索引、長度獲取等。
可是注意一點,sort和reverse方法返回的是一個新的Observable Arrays,對本來的類數組不會產生影響,這一點和原生數組不同。

請記住,這個類數組無論和真實的數組有多麼類似,它都不是一個真正的原生數組,因此毫無疑問Array.isArray(observable([]))的返回值都是false。當你須要將這個Observable Arrays轉換成真正的數組時,可使用slice方法建立一個淺拷貝。換句話來講,Array.isArray(observable([]).slice())會返回true。

除了原生數組支持的API外,Observable Arrays還支持如下API:

  • intercept(interceptor)
    這個方法能夠在全部數組的操做被應用以前,將操做攔截。具體的請看Intercept & Observe

  • observe(listener, fireImmediately? = false)
    用來監聽數組的變化(相似ES7中的observe,惋惜這個ES7中的observe將被廢棄),它返回一個用以註銷監聽器的函數。

  • clear()
    清空數組

  • replace(newArray)
    用一個新數組中的內容來替換掉原有的內容

  • find(predicate: (item, index, array) => boolean, thisArg?, fromIndex?)
    基本上與ES7 Array.find的提案相同,不過多了fromIndex參數。

  • remove(value)
    移除數組中第一個值等於value的元素,若是移除成功,則會返回true

  • peek()
    和slice相似,但它不會建立保護性拷貝,因此性能比slice會更好。若是你可以肯定,轉換出的數組確定僅以只讀的方式使用,那麼可使用這個API

總結

Mobx想要入門上手能夠說很是簡單,只須要記住少許概念並能夠完成許多基礎業務了。但深刻學習下去,也仍是要接觸許多概念的。例如Modifier、Transation等等。
最後與Redux作一個簡單的對比

  1. Mobx寫法上更偏向於OOP
  2. 對一份數據直接進行修改操做,不須要始終返回一個新的數據
  3. 對typescript的支持更好一些
  4. 相關的中間件不多,邏輯層業務整合是一個問題
做者:緋色流火連接:https://www.jianshu.com/p/505d9d9fe36a來源:簡書著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
相關文章
相關標籤/搜索