前端的場景愈來愈複雜,現階段,新項目都會採用Vue
、Angular
、React
之一來管理數據到視圖的映射關係,它們都有本身管理組件狀態、生命週期的獨特機制,可是在複雜場景下,仍是會採用像Vuex
、Ngrx
、Redux
這樣的狀態容器來管理重要的全局狀態。前端
我工做最主要用的仍是React
,在項目中,我使用Mobx
來做爲React
狀態管理的補充,加快編碼的效率,本文主要記錄一些Mobx
的用法。react
Mobx
是一個狀態管理庫,在狀態依賴的描述上面有獨特的優點,就像是在寫公式同樣,它能讓開發者更簡潔的聲明描述屬性狀態的依賴關係,自動的完成相關依賴的更新、引發反作用。數組
Mobx
的使用很靈活,能夠將observable
的特性直接做用在一個對象中,也能夠聲明在類中,甚至直接寫入React
組件類的屬性中(在Mobx
的視角中與類沒有區別)。性能優化
直接使用 observable
包裝的對象,會得到Mobx
給予的能力。bash
import * as mobx from "mobx";
// 聲明一個對象是 observable
const myObj = mobx.observable({
a: 1,
b: 3,
get c() {
return this.b * 2;
}
});
// 註冊一個反作用函數
mobx.autorun(() => {
console.log("a", myObj.a);
});
mobx.autorun(() => {
console.log("c", myObj.c);
});
// 改變這個對象的屬性
myObj.a = false;
myObj.a = "hello";
myObj.b = 4;
/** 依次輸出 a 1 c 6 a false a hello c 8 */
複製代碼
已經能夠看出Mobx
的一些特性了dom
myObj.b
,變化時不會觸發只與a
有關的反作用。c
這個getter
屬性會被autorun
反作用記錄到關於b
的依賴,當b
發生變化,關聯c
的反作用也會被觸發。知道以上的規則,就能夠直接在項目中嘗試它了。異步
observable
import { observable, computed, action, autorun, flow } from "mobx";
// 使用屬性裝飾器聲明
class SimpleStore {
@observable a = 1;
@observable c = 2;
@computed get b() {
return this.a * this.a + 1;
}
@action setA(a) {
this.a = a;
}
asyncUpdate = flow(function*() {
const next1 = yield new Promise(res => setTimeout(() => res(3), 1000));
this.a = next1;
const next2 = yield new Promise(res => setTimeout(() => res(4), 1000));
this.a = next2;
});
}
const store = new SimpleStore();
autorun(() => {
console.log(store.a);
});
store.setA(2);
store.asyncUpdate();
/** 依次輸出 1 2 // setA(2) 3 // asyncUpdate() 1s 4 // asyncUpdate() 2s */
複製代碼
從類的observable
聲明中又能看出一些東西來:async
Mobx
相關的功能,相比直接使用對象,須要輸入的工做量會大一些,可是能夠對其運做有更細粒度的控制observable
的能力,在字段上加上@observable
,該字段就會被反作用記錄到。@action
是用來聲明改變@observable
字段的方法。若是開啓瞭如下配置,將強制使用@action
方法來修改屬性,不然會報錯。mobx.configure({
enforceActions: true
});
複製代碼
flow
是 Mobx
提供用於修飾異步action
的方法。其實就是一個async/await
方法的Generator
實現,最棒的特性就是這個方法返回一個 Promise,是能夠取消的。react
組件中使用 Mobx
由於 react
組件須要監聽 observable
的變化, render
的邏輯其實就是反作用,使用 autorun
的正確方式是引入 mobx-react
庫。導入 observer
這個高階組件,來自動完成 autorun
的註冊與銷燬。ide
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer, Observer } from "mobx-react";
@observer // 高階組件,讓react組件的render在mobx的autorun上下文中運行
class Counter extends React.Component {
// 能夠直接使用 observable 裝飾使用,代替 react 本身的 state,更新屬性比setState要直接一些
@observable count = 0;
@observable unused = 0;
handleInc = () => this.count++;
handleDec = () => this.unused--;
render() {
console.log("render");
return (
<div>
{this.count}
<button onClick={this.handleInc}>+</button>
<button onClick={this.handleDec}>-</button>
</div>
);
}
}
複製代碼
上例中,對 count
的更新會強制組件更新,對 unused
的更新不會致使從新渲染,由於 render
僅僅聲明瞭對 count
的使用, render
又被高階組件用 autorun
包裝過,autorun
其實有返回值,用於銷燬這個反作用,不過被 react
的 unmount
生命週期自動銷燬了。函數
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer, Observer, useLocalStore } from "mobx-react";
// 使用 observer 直接包裝函數組件
const Counter = observer(() => {
// 使用 useLocalStore 建立一個局部的 observable
const local = useLocalStore(() => ({
count: 0,
unused: 0,
handleInc() {
this.count++;
},
handleDec() {
this.unused--;
}
}));
console.log("render");
return (
<div>
{local.count}
<button onClick={() => local.handleInc()}>+</button>
<button onClick={() => local.handleDec()}>-</button>
</div>
);
});
複製代碼
注意:使用 observer
: 這個 autorun
的上下文僅僅用於當前 render
直接訪問的屬性,若是對 observable
屬性的訪問發生在子元素的 props
且爲函數時,須要手動使用 <Observer render={()=><JSX>...</JSX>}/>
將其放入新的 autorun
上下文中,不然更新不會生效。
observable
對象能夠直接在外面建立 observable
對象或類,再用 observer
消費它。這裏介紹一下使用全局 Store
的方式。
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable } from "mobx";
import { observer, Observer, useLocalStore } from "mobx-react";
const store1 = observable({
a: 1,
b: "hello",
incA() {
this.a++;
},
repeatB() {
this.b += this.b;
},
asyncIncA() {
setTimeout(() => {
this.a++;
}, 1000);
}
});
// 主要代碼
//// {
const stores = {
store1
};
type TStore = typeof stores;
const storeCtx = React.createContext<TStore>(stores);
const StoreProvider = ({ children }) => (
<storeCtx.Provider value={stores}>{children}</storeCtx.Provider>
);
const useSore = () => React.useContext(storeCtx);
//// }
// 主要代碼
const UsingStore = observer(() => {
const { store1 } = useSore();
return (
<div>
<div>a:{store1.a}</div>
<div>b:{store1.b}</div>
<button onClick={() => store1.incA()}>incA</button>
<button onClick={() => store1.asyncIncA()}>asyncIncA</button>
<button onClick={() => store1.repeatB()}>repeatB</button>
</div>
);
});
@observer
class UsingStoreInClass extends React.Component {
static contextType = storeCtx;
render() {
const { store1 } = this.context as TStore;
return (
<div>
<div>a:{store1.a}</div>
<div>b:{store1.b}</div>
<button onClick={() => store1.incA()}>incA</button>
<button onClick={() => store1.repeatB()}>repeatB</button>
</div>
);
}
}
const App = () => {
return (
<>
<StoreProvider>
<UsingStore />
<br />
<UsingStoreInClass />
<br />
</StoreProvider>
</>
);
};
複製代碼
主要的代碼段就是建立一個 stores
並放入 Context
,以後類組件和函數組件都用 observer
裝飾,從 Context
拿出這個全局狀態使用,一旦這個全局狀態有更新,相關的組件都會被通知到並從新 render
。
這裏仍是要多說一句,不要把應用的所有狀態放在全局 Store
裏面,這樣狀態管理的難度會大大增長,內存資源的釋放每每不到位,應該交由局部的狀態讓 react
的生命週期函數來幫咱們作這些事情,尤爲是 React v16.8
提供的 Hooks
就是不錯的選擇 ,應該只把一些 關鍵的全局可變狀態 放入全局 Store
,好比用戶信息。
當使用 observable
包裝一個對象或屬性時,會遞歸的將其轉換成 observable
,在 console.log
查看調試的時候很不方便,充滿了 Proxy
(若是用的是 Mobx 5.x
),可使用 mobx.toJS
來將其轉換成一個普通的對象
有些時候,遞歸將屬性轉成 observable
粒度太細了,很不必,其實也能夠減小這部分的 Proxy
開銷,方法是使用對屬性使用 observable.ref
、observable.shallow
,或者對屬性直接用 observable.object
、observable.array
、observable.map
建立時傳入 option
{deep:false}
來調節。
一些複雜場景下,計算屬性每每是根據依賴異步獲取的,使用 computed
顯得不合適,可使用多個 observable
並用 reaction
來執行獲取邏輯。
import * as React from "react";
import * as ReactDOM from "react-dom";
import { observable, reaction, autorun } from "mobx";
import { observer, Observer, useLocalStore } from "mobx-react";
class ChainDemo {
@observable a = 0;
@observable b = 0;
@observable c = 0;
@observable d = 0;
init = () => {
const disposer = [
reaction(
() => {
const val = this.a;
return new Promise<number>(res =>
setTimeout(() => res(val + 1), 100)
);
},
async p => {
this.b = await p;
}
),
reaction(
() => {
const val = this.b;
return new Promise<number>(res =>
setTimeout(() => res(val + 1), 100)
);
},
async p => {
this.c = await p;
}
),
reaction(
() => {
const val = this.c;
return new Promise<number>(res =>
setTimeout(() => res(val + 1), 100)
);
},
async p => {
this.d = await p;
}
)
];
return () => disposer.forEach(d=>d());
};
}
const chain = new ChainDemo();
chain.init();
autorun(() => {
console.log(chain.a, chain.b, chain.c, chain.d);
});
chain.a = 2;
/**
* 0 0 0 0
* 2 0 0 0
* 2 3 0 0
* 2 3 4 0
* 2 3 4 5
* /
複製代碼
上例中使用了幾個延遲計算取值,狀態根據咱們描述的 react
鏈逐步更新,變化快時能夠配合 flow
和 debounce
作更加細粒度,可控的性能優化。
react
生命週期管理局部的 observable
狀態接着上面的代碼繼續看這個例子,一系列的 reaction
返回了不少的 disposer
用於銷燬反作用,因此把這個 init
直接放在 useEffect
去執行簡直是完美,利用組件的生命週期完成狀態的初始化和銷燬。
const Comp = observer(() => {
const [state] = useState(() => new ChainDemo());
useEffect(state.init, [state]);
});
複製代碼
以爲不錯就點個贊吶~