一、Mobx機制介紹:
Mobx 是簡單、可擴充的狀态管理,當應用狀态更新時,任何源自應用狀态的東西都将自動地獲得。React 和 MobX 是一對強力組合。React 通過提供機制把應用狀态轉換為可渲染元件樹并對其進行渲染。而 MobX 提供機制來存儲和更新應用狀态供 React 使用。
Mobx 中文文檔
Mobx 英文文檔
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、公衆号:公衆号「進軍全棧攻城獅」 ,對前端技術保持學習愛好者。我會經常分享自己所學所看的幹貨,在進階的路上,共勉!