Mobx 我的理解

什么是MobX
官方圖


image.png

網(wǎng)圖


安裝

1.安裝MobX:yarn add mobx --save

2.安裝React綁定庫:yarn add mobx-react --save

3.啟用裝飾器語法(能夠使用@標(biāo)簽)

第一步:

yarn add babel-plugin-transform-decorators-legacy babel-preset-react-native-stage-0 --save-dev

第二步:在.babelrc文件中修改為

{
  "presets": ["react-native"],
  "plugins": ["transform-decorators-legacy"]
}
MobX常用標(biāo)簽
@observable : 使用此標(biāo)簽監(jiān)控要檢測的數(shù)據(jù); 
@observer: 使用此標(biāo)簽監(jiān)控當(dāng)數(shù)據(jù)變化是要更新的Component(組件類) 
autorun : 當(dāng)觀測到的數(shù)據(jù)發(fā)生變化的時(shí)候,如果變化的值處在autorun中,那么autorun就會(huì)自動(dòng)執(zhí)行。 
@action : 使用此標(biāo)簽監(jiān)控?cái)?shù)據(jù)改變的自定義方法(當(dāng)在需要數(shù)據(jù)改變的時(shí)候執(zhí)行此自定義方法,那么View層也會(huì)跟著自動(dòng)變化,默認(rèn)此View層已經(jīng)使用@observer標(biāo)簽監(jiān)控) 
useStrict : 使用嚴(yán)格模式 
@computed : 計(jì)算值(computed values)是可以根據(jù)現(xiàn)有的狀態(tài)或其它計(jì)算值衍生出的值,可以定義在相關(guān)數(shù)據(jù)發(fā)生變化時(shí)自動(dòng)更新的值.
Observable
用法
observable(value)
或
@observable classProperty = value

可以理解為被觀察者對象,其值可以是JS基本數(shù)據(jù)類型、引用類型、普通對象、類實(shí)例、數(shù)組和映射。
注意:如果value是沒有原型的對象(例如:{key: 'key', value: 'value'}普通對象是指不是使用構(gòu)造函數(shù)創(chuàng)建出來的對象,而是以 Object 作為其原型,或者根本沒有原型。 ),則該對象會(huì)被克隆,并且其所有的屬性都會(huì)被轉(zhuǎn)換為可觀察的;如果value是有原型的對象(例如:new MyClass()),MobX 不會(huì)將一個(gè)有原型的對象自動(dòng)轉(zhuǎn)換成可觀察的,在該類中的構(gòu)造函數(shù)中使用extendObservable或在類定義上使用 @observable / decorate

對observable的變化做出響應(yīng)

類似于Vue中的計(jì)算屬性,根據(jù)現(xiàn)有被觀察的對象計(jì)算衍生出新的值。

import {observable, computed} from "mobx"

class Cart {
    @observable price = 0;
    @observable amount = 1;

    constructor(price) {
        this.price = price;
    }

    @computed get total() {
        return this.price * this.amount;
    }
}

若前一個(gè)計(jì)算中使用的數(shù)據(jù)沒有更改,計(jì)算屬性將不會(huì)重新運(yùn)行;當(dāng)其有觀察者的時(shí)候才會(huì)重新計(jì)算;如果一個(gè)計(jì)算屬性不在被觀察了(使用它的UI被銷毀了),Mobx會(huì)自動(dòng)將其回收。

2.autorun

不需要被觀察者觀察,創(chuàng)建的時(shí)候被觸發(fā)一次,然后每次它的依賴關(guān)系改變時(shí)會(huì)再次被觸發(fā) ,常用作打印日志,更新UI等。 autorun 中的值必須要手動(dòng)清理才行 ,傳遞給 autorun 的函數(shù)在調(diào)用后將接收一個(gè)參數(shù),即當(dāng)前 reaction(autorun),可用于在執(zhí)行期間清理 autorun

var numbers = observable([1,2,3]);
var sum = computed(() => numbers.reduce((a, b) => a + b, 0));

var disposer = autorun(() => console.log(sum.get()));
// 輸出 '6'
numbers.push(4);
// 輸出 '10'

// 清理autorun 
disposer();
numbers.push(5);
// 不會(huì)再輸出任何值。`sum` 不會(huì)再重新計(jì)算。
3.observer

observer 是由單獨(dú)的 mobx-react 包提供的 ,observer 函數(shù)/裝飾器可以用來將 React 組件轉(zhuǎn)變成響應(yīng)式組件

import {
  observable,
  computed,
  autorun,
  action
} from 'mobx'

class AppState {

  @observable timer = 0

  // 注意這里不能調(diào)用super(props),否則報(bào)錯(cuò)
  constructor (props) {
    setInterval(() => {
      this.timerIncreat(this.timer)
    }, 1000)
  }

  @action timerIncreat (time) {
    this.timer = time += 1
  }

  // 重置計(jì)數(shù)器
  @action resetTimer () {
    this.timer = 0
  }
}

export default AppState
import React, {Component} from 'react'
import {View, StyleSheet} from 'react-native'
import {Button, Label} from 'teaset'

import AppState from '../mobx/AppState'
import { observer } from 'mobx-react'

// 實(shí)例化,也可在導(dǎo)出的時(shí)候?qū)嵗?const appState = new AppState()

// 這里必須要寫,不然監(jiān)聽不到值的變化
@observer
export default class MobxDemo1 extends Component {

  static navigationOptions = ({navigation, screenProps}) => ({
      headerTitle: 'Mobx練習(xí)一'
  })

  render () {
    return (
      <View style={styles.container}>
        <Label style={styles.welcome} text="計(jì)數(shù)器的一個(gè)Mobx例子"/>
        <View style={{flexDirection: 'row', justifyContent: 'space-around', marginTop: 40}}>
          <Label style={styles.welcome} text={`當(dāng)前的值是:${appState.timer}`}/>
          <Button type='primary' size='md' title='重置' onPress={this.onReset}/>
        </View>
      </View>
    )
  }

  onReset () {
    appState.resetTimer()
  }
}

const styles = StyleSheet.create({
  container: {
      flex: 1,
      // justifyContent: 'center',
      backgroundColor: 'white',
      alignItems: 'center'
  },
  welcome: {
      marginTop: 20,
      fontSize: 20, 
  }
})
改變observable的值
  1. action

顧名思義就是動(dòng)作,所有被觀察的對象被修改都應(yīng)該通過動(dòng)作函數(shù)來進(jìn)行修改

用法有以下幾種:
action(fn)
action(name, fn)
@action classMethod() {}
@action(name) classMethod () {}
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}

2.異步action
action只能影響正在運(yùn)行的函數(shù),而無法影響當(dāng)前函數(shù)調(diào)用的異步操作 。action 包裝/裝飾器只會(huì)對當(dāng)前運(yùn)行的函數(shù)作出反應(yīng),而不會(huì)對當(dāng)前運(yùn)行函數(shù)所調(diào)用的函數(shù)(不包含在當(dāng)前函數(shù)之內(nèi))作出反應(yīng) ,也就是說promise的then或async語句,并且在回調(diào)函數(shù)中某些狀態(tài)改變了,這些回調(diào)函數(shù)也應(yīng)該包裝在action中。
(1)第一種方案,使用action關(guān)鍵字來包裝promises的回調(diào)函數(shù)。
// 第一種寫法

class Store {

@observable githubProjects = []
@observable state = "pending" // "pending" / "done" / "error"

@action
fetchProjects() {
    this.githubProjects = []
    this.state = "pending"
    fetchGithubProjectsSomehow().then(
        // 內(nèi)聯(lián)創(chuàng)建的動(dòng)作
        action("fetchSuccess", projects => {
            const filteredProjects = somePreprocessing(projects)
            this.githubProjects = filteredProjects
            this.state = "done"
        }),
        // 內(nèi)聯(lián)創(chuàng)建的動(dòng)作
        action("fetchError", error => {
            this.state = "error"
        })
    )
 }
}

// 第二種寫法

 class Store {
     @observable githubProjects = []
     @observable state = "pending" // "pending" / "done" / "error"

     @action
     fetchProjects() {
         this.githubProjects = []
         this.state = "pending"
         fetchGithubProjectsSomehow().then(
             projects => {
                 const filteredProjects = somePreprocessing(projects)
                 // 將修改放入一個(gè)異步動(dòng)作中
                 runInAction(() => {
                     this.githubProjects = filteredProjects
                     this.state = "done"
                  })
             },
             error => {
                 runInAction(() => {
                     this.state = "error"
                 })
             }
         )
     }
 }

第二種方案,用async function來處理業(yè)務(wù),那么我們可以使用runInAction這個(gè)API來解決之前的問題 。

import {observable, action, useStrict, runInAction} from 'mobx';
useStrict(true);

class Store {
  @observable name = '';
  @action load = async () => {
    const data = await getData();
    // await之后,修改狀態(tài)需要?jiǎng)幼?    runInAction(() => {
      this.name = data.name;
    });
  }
}
  1. flows
    然而,更好的方式是使用 flow 的內(nèi)置概念。它們使用生成器。一開始可能看起來很不適應(yīng),但它的工作原理與 async / await 是一樣的。只是使用 function * 來代替 async,使用 yield 代替 await 。 使用 flow 的優(yōu)點(diǎn)是它在語法上基本與 async / await 是相同的 (只是關(guān)鍵字不同),并且不需要手動(dòng)用 @action 來包裝異步代碼,這樣代碼更簡潔。

flow 只能作為函數(shù)使用,不能作為裝飾器使用。 flow 可以很好的與 MobX 開發(fā)者工具集成,所以很容易追蹤 async 函數(shù)的過程。

mobx.configure({ enforceActions: true })

class Store {
    @observable githubProjects = []
    @observable state = "pending"

    fetchProjects = flow(function * () { // <- 注意*號,這是生成器函數(shù)!
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = yield fetchGithubProjectsSomehow() // 用 yield 代替 await
            const filteredProjects = somePreprocessing(projects)
            // 異步代碼塊會(huì)被自動(dòng)包裝成動(dòng)作并修改狀態(tài)
            this.state = "done"
            this.githubProjects = filteredProjects
        } catch (error) {
            this.state = "error"
        }
    })
}
實(shí)際中的運(yùn)用

跨組件交互
在不使用其它框架、類庫的情況下,React要實(shí)現(xiàn)跨組件交互這一功能相對有些繁瑣。通常我們需要在父組件上定義一個(gè)state和一個(gè)修改該state的函數(shù)。然后把state和這個(gè)函數(shù)分別傳到兩個(gè)子組件里,在邏輯簡單,且子組件很少的時(shí)候可能還好,但當(dāng)業(yè)務(wù)復(fù)雜起來后,這么寫就非常繁瑣,且難以維護(hù)。而用Mobx就可以很好地解決這個(gè)問題。來看看以下的例子:

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>
    );
  }
}

有兩個(gè)子組件,Main和AllNum (均采用無狀態(tài)函數(shù)的方式聲明的組件)
在MyState中存放了這些組件要用到的所有狀態(tài)和函數(shù)。
之后只要在父組件需要的地方實(shí)例化一個(gè)MyState對象,需要用到數(shù)據(jù)的子組件,只需要將這個(gè)實(shí)例化的對象通過props傳下去就好了。

那如果組件樹比較深怎么辦呢?
我們可以借助React15版本的新特性context來完成。它可以將父組件中的值傳遞到任意層級深度的子組件中。
總結(jié)
Mobx想要入門上手可以說非常簡單,只需要記住少量概念并可以完成許多基礎(chǔ)業(yè)務(wù)了。但深入學(xué)習(xí)下去,也還是要接觸許多概念的。例如Modifier、Transation等等。
最后與Redux做一個(gè)簡單的對比

Mobx寫法上更偏向于OOP
對一份數(shù)據(jù)直接進(jìn)行修改操作,不需要始終返回一個(gè)新的數(shù)據(jù)
對typescript的支持更好一些
相關(guān)的中間件很少,邏輯層業(yè)務(wù)整合是一個(gè)問題

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容