天天看點

React + MobX 快速上手

一、Mobx機制介紹:

Mobx 是簡單、可擴充的狀态管理,當應用狀态更新時,任何源自應用狀态的東西都将自動地獲得。React 和 MobX 是一對強力組合。React 通過提供機制把應用狀态轉換為可渲染元件樹并對其進行渲染。而 MobX 提供機制來存儲和更新應用狀态供 React 使用。

Mobx 中文文檔

Mobx 英文文檔

 Mobx 的運作機制如下圖:

React + MobX 快速上手

首先從左往右看,事件觸發了 Actions,Actions 作為唯一修改 State 的方式,修改了 State,State 的修改更新了計算值 Computed,計算值的改變引起了 Reactions 的改變,導緻了 UI 的改變,Reactions 可以經過事件調用 Actions。

二、開發環境

安裝 mobx 和 React 綁定庫 mobx-react:

npm install mobx mobx-react --save
           

要啟用 ESNext 的裝飾器 (可選), 參見下面:

  • https://unpkg.com/mobx/lib/mobx.umd.js
  • https://cdnjs.com/libraries/mobx

安裝修飾符修飾器插件

// 修飾符的插件
npm install --save-dev babel-plugin-transform-decorators-legacy

// 裝飾器的一個插件
npm install @babel/plugin-proposal-decorators
           

配置babelrc檔案,在根目錄建立一個.babelrc檔案(存在.babelrc檔案的直接進行配置), 輸入以下内容:

{
    "plugins":[
        [
          "@babel/plugin-proposal-decorators",
          {
              "legacy":true
          }
        ],
        [
          "@babel/plugin-proposal-class-properties",
          {
              "loose":true
          }
          ]
          ],
    "presets":[
        "react-app"
        ]

}
           

三、常用API介紹及用法

1、@observable (定義變量狀态):

observable

 用于定義可觀察狀态,觀測的資料可以是數字、字元串、數組、對象等。

使用@observable定義的變量是可以被修改的,如果是常量或者不可再被修改的變量就可以不用@observable修飾

例:從mobx中引入observable,  import {observable} from 'mobx'; 使用如下:

@observable count: number = 0;
@observable tool: string = "";
sex: string = 'nan';
@observable classCurrentId: string = '';
@observable classDataList: any = [];
           

注意:被 

observable

 觀測資料的修改是同步的,不像 setState 那樣是異步。

2、@observer (觀察者):被observer修飾的元件,将會根據元件内使用到的被observable修飾的state的變化而自動重新渲染

一般用在修飾類class前面,内部有可觀察的變量的話(通過@observable定義的),添加@observer,即可實時觀察資料變化,重新render

例:從mobx-react中引入observer,import {observer} from "mobx-react";

如下: 隻要store中的count變化了,下面這個Timer元件就會重新render,重新更新,這就是@observer的用途

import store from './mobx/store';//引入store

@observer 
class Timer extends React.Component {
    render() {
        return (
            <div>{store.count}</div>
        )
    }
};
           

3、reaction (監聽):元件中通過reaction監聽store資料變化,變化之後在元件内部作出相關響應

可以監聽一個store變量,也可以監聽多個store變量,如下所示

監聽一個的例子:

// 監聽classCurrentId的變化,newClassId為最新的資料
reaction(() => store.mobTest.classCurrentId, (newClassId) => {
    //dosomething
})


// 監聽classDataListLength變化, newClassDataList為最新的資料
reaction(() => store.mobTest.classDataListLength, (newClassDataList) => {
    //dosomething
});
           

銷毀:

let disposeOrgChange = reaction(
      () => this.global.currentOrgInfo.id,
      (id) => {
          if (id) {
             //doSomething
          }
       }
);

在組建銷毀的時候執行
this.disposeOrgChange && this.disposeOrgChange();
           

監聽多個變量例子:

/**
   監聽screenMode和ClassType.type的變化,下面newData為最新資料,可以通過
   newData.mode, newData.type擷取最細的資料
/*

reaction(() => ({ mode: store.home.screenMode, type: store.ClassType.type }), newData => {
      //doSomething
})
           

4、toJS():将觀察者對象轉換成正真定義的格式,如将觀察者數組轉換成數組,将觀察者對象轉換成對象

如:在store中定義了一個可修改的變量為數組格式:@observable answerList: any[] = []; 我們在擷取最新answerList資料的時候,拿到的不是真正意義上的數組,是被觀察着數組,這時候我們必須通過toJS轉換一下就可以了

例子:

// answerList為store中定義的被觀察的數組,使用reaction監聽的時候,通過toJS轉換成真正的數組
reaction(() => toJS(store.answer.answerList), newValue => {
    //doSomething
})
           

5、@inject (注入):注入store中的方法和變量,然後元件裡就可以直接通過this.props.xxx的方式使用其他store中的變量或方法

使用inject注入要注意,需要在跟元件将App元件使用Provider包裹,如下

import { Provider } from 'mobx-react'

//store為定義的mobx引入的store檔案
class App extends Component{
    render(){
        return(
            <Provider store={store}>
                <ToDoApp/>
            </Provider>
        )
    }
}
           

元件中使用案例:

import store from '../mobx/store'; //直接引入store

interface IProps {
  store?: any; //通過App傳入的store
}

interface IState {
}

@inject('store');//注入所有store
class PokeButton extends React.Component<IProps, IState> {
    //doSomething 元件裡面就可以通過 this.props.store.xxx擷取任意store資料
}
           

6、@computed (計算屬性):使用@computed修飾的方法,内部隻要有一個變量變化,就會重新計算,會在依賴的狀态發生變化時會重新運作

注意點:

computed 值會被緩存:每當讀取 computed 值時,如果其依賴的狀态或其他 computed 值未發生變化,則使用上次的緩存結果,以減少計算開銷。

computed 值會惰性計算:隻有 computed 值被使用時才重新計算值。反之,即使 computed 值依賴的狀态發生了變化,但是它暫時沒有被使用,那麼它不會重新計算。

例子:

//store中定義  
@computed get classDataListLength() {
    return this.classDataList.length;
}
           

使用:classDataListLength變化時執行

// 監聽mobx 班級選中清單元素變化
reaction(() => store.mobTest.classDataListLength, (newClassDataList) => {
    //doSomething
});
           

7、autorun(自定義反應):用于定義響應函數,并在定義時立即執行一次。 以後,每當依賴狀态發生變化時,

autorun

 自動重新運作。

當你想建立一個響應式函數,而該函數本身永遠不會有觀察者時,可以使用

mobx.autorun

。 這通常是當你需要從反應式代碼橋接到指令式代碼的情況,例如列印日志、持久化或者更新UI的代碼。 當使用

autorun

時,所提供的函數總是立即被觸發一次,然後每次它的依賴關系改變時會再次被觸發。 相比之下,

computed(function)

建立的函數隻有當它有自己的觀察者時才會重新計算,否則它的值會被認為是不相關的。 經驗法則:如果你有一個函數應該自動運作,但不會産生一個新的值,請使用

autorun

。 其餘情況都應該使用

computed

。 Autoruns 是關于 啟動效果 (initiating effects) 的 ,而不是産生新的值。 如果字元串作為第一個參數傳遞給

autorun

,它将被用作調試名。

傳遞給 autorun 的函數在調用後将接收一個參數,即目前 reaction(autorun),可用于在執行期間清理 autorun。

就像@observer 裝飾器/函數,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'

disposer();
numbers.push(5);
// 不會再輸出任何值。`sum` 不會再重新計算。
           

8、@action(動作):用于定義狀态修改操作

常用案例:隻有通過@action才可以修改store資料

import {observable, action} from 'mobx';

class AnswerData {
  @observable currentState: string = '';
  @observable micList: any[] = [];
  @observable teaStatus: string = 'set';
  @observable timer: number = 0;
  timerId: any = null;

  startTimer = () => {
    this.timerId = setInterval(() => {
      this.timer += 1
    }, 1000);
  }

  @action changeState (state: string) {
    this.currentState = state;
  }

  @action setList (l: any[]) {
    this.micList = l;
    this.startTimer();
  }

  @action teaQuizEnd = () => {
    this.teaStatus = 'reStart';
  }
}

export const answer = new AnswerData();
           

異步操作處理:

@action

隻會對目前運作的函數做出反應,對于一些不在目前函數中調用的回調是無效的,例如

setTimeout

回調、

Promise

then

async

語句。這些回調如果修改了狀态,也應該用

action

包裹起來。以下方法可處理該問題:

修改狀态的語句用 

runInAction

 包裹起來,

runInAction包裹執行完成之後再修改

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

    @action
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            // await 之後,再次修改狀态需要動作:
            runInAction(() => {
                this.state = "done"
                this.githubProjects = filteredProjects
            })
        } catch (error) {
            runInAction(() => {
                this.state = "error"
            })
        }
    }
}
           

用法:

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隻能影響正在運作的函數,而無法影響目前函數調用的異步操作

@action createRandomContact () {
    superagent.get('https://randomuser.me/api/').set('Accept', 'application/json')
      .end(action("createRandomContact-callback", (error: any, results: any) => {
        if (error) {
          console.error(error);
        } else {
          console.log(results)
        }
    }));
  }
           

在 end 中觸發的回調函數,被 action 給包裹了,這就很好驗證了上面的那句話,action 無法影響目前函數調用的異步操作,而這個回調毫無疑問是一個異步操作,是以必須再用一個 action 來包裹住它,這樣程式才不會報錯。

如果你使用

async function

來處理業務,那麼我們可以使用

runInAction

這個 API 來解決問題。

import {observable, action, runInAction} from 'mobx';

class Store {
  @observable name = '';
  @action load = async () => {
    const data = await getData();
    runInAction(() => {
      this.name = data.name;
    });
  }
}
           

你可以把 

runInAction

 有點類似 

action(fn)()

 的文法糖,調用後,這個 

action

 方法會立刻執行。

交流

1、QQ群:可添加qq群共同進階學習: 進軍全棧工程師疑難解  群号:   856402057

2、公衆号:公衆号「進軍全棧攻城獅」 ,對前端技術保持學習愛好者。我會經常分享自己所學所看的幹貨,在進階的路上,共勉!

React + MobX 快速上手