日常使用mobx的小技巧
由於自己開發的項目都是中小型項目,所以在技術選型上使用了mobx。但是使用過程中發現關於mobx的技術文章並不多。於是萌發出寫這篇文章的想法。請輕噴。
- 更新控制store渲染的方法
mobx一些有關資料
添加mobx
npm i mobx
npm i mobx-react
mobx用來操作store(也就是數據操作層,model層),而mobx-react則是用來操作view(也就是視圖層,Component層)。
mobx常用操作符
- observable,將
JS
基本數據類型、引用類型、普通對象、類實例、數組和映射,轉換為可觀察數據。 - action,用來修改
observable
的數據的動作,只有action
和runInAction
才能修改observable
。 - runInAction,用來在異步的時候執行修改
observable
的數據的動作。例如網絡請求后修改數據。 - computed,根據現有的
observable
的值或其它計算值衍生出的值。只有在view
使用了computed
的值,computed
才會執行計算
mobx-react常用操作符
- observer,將
React組件
轉變成響應式組件。 - inject,將組件連接到提供的
stores
。一般是用來連接到上層組件提供的store
或者全局store
。 - Provider,它是一個
react組件
,用來向下傳遞stores
。任意子組件可以使用inject
來獲取Provider
的store
。
代碼
下面會貼點自己的代碼。希望能給大家帶來一些幫助。
全局store
// 文件 index.jsx
import { Provider } from "mobx-react";
import * as stores from "../../stores";
class MainView extends Component {
render() {
return (
<React.Fragment>
<Provider {...stores}> // 這里是全局stores的配置
<Switch>
{routers.map((item, index) => {
return (
<Route
exact
key={item.path}
path={item.path}
component={item.component}
/>
);
})}
</Switch>
</Provider>
</React.Fragment>
);
}
}
// 文件 ../../stores/index.js
import aStore from "./aStore";
import bStore from "./bStore";
export { aStore, bStore};
// 文件 aStore.js
class AStore {
@observable info = {};
@observable line = {};
@action
onInfo = data => {
this.info = data;
};
@action
onLine = data => {
this.line = data;
};
}
const aStore = new AStore();
export default aStore;
// 組件使用aStore
@inject(all => ({
aStore: all.aStore // 連接到aStore
}))
@observer
class Detail extends Component {
updateInfo = () => {
const aStore = this.props.aStore;
aStore.onInfo(info) // 改變store屬性
}
render() {
const aStore = this.props.aStore; // 獲取到全局store
return (
<div>
{aStore.info.name} // 使用里面的屬性
<Button onClick={this.updateInfo}>改變info</Button>
</div>
);
}
}
組件內可觀察數據。
@observer
class Detail extends Component {
@observable info = {};
@observable index = 1;
@computed get sum() {
return index * 4
}
@action
onInfo = data => {
this.info = data;
};
updateInfo = () => {
this.onInfo(info) // 改變store屬性
}
render() {
return (
<div>
{this.info.name} // 使用里面的屬性
<Button onClick={this.updateInfo}>改變info</Button>
</div>
);
}
}
最后一種是我自己項目使用的,個人覺得不錯。可以分離邏輯層和視圖層。如果想使用全局stores
,直接用inject
導入第一種方式注入的stores
。能較好划分全局stores
和單業務store
的職責,而不是無腦的以樹的方式全掛在index.js
上面。子組件
最好跟父組件
用同一個store
,方便溝通的同時層級不多也不會導致store
太過復雜。
// 邏輯層 用來處理業務邏輯
import { observable, runInAction } from "mobx";
import { aService, bService } from "../../../services/dispatch";
import { T } from "react-toast-mobile";
class ListStore {
@observable list = [];
serInitData = async () => {
try {
T.loading();
const data = await aService()
runInAction("serInitData", () => {
this.list = data || [];
});
} catch (error) {
T.notify(error.message);
console.error(error);
} finally {
T.loaded();
}
};
serBService = async vehicleNum => {
try {
T.loading();
await bService(vehicleNum);
runInAction("serBService", () => {
this.list = this.list.filter(params => {
return params.vehicleNum != vehicleNum;
});
});
} catch (error) {
T.notify(error.message);
// eslint-disable-next-line no-console
console.error(error);
} finally {
T.loaded();
}
};
}
export default ListStore;
// 視圖層 純粹的視圖展示和操作觸發
@observer
class Detail extends Component {
store = new ListStore();
componentDidMount() {
// 初始化數據
this.store.serInitData();
}
updateInfo = () => {
this.store.serBService(); // 調用bService
}
render() {
return (
<div>
{this.store.list.map((item)=>{
return
<Provider myStore={this.store} >
<Item key={item.guid} />
</Provider>
})} // 輪詢列表
<Button onClick={this.updateInfo}>改變info</Button>
</div>
);
}
}
const Item = inject(allStores => ({
aStore: all.aStore, // 連接到aStore
myStore: allStores.myStore, // 連接到父組件的myStore
}))(
observer(function(props) { // 使用function方式在發送action等操作時,會帶上Item這個組件名字,能變相的看到發送的來源。使用箭頭函數則不會。而且react推薦的無狀態組件也是使用的function
return (
<div>
{this.props.aStore.xxxxx} // 使用全局store
{this.props.myStore.xxxxx} // 使用父組件的store
</div>
);
}));
一些備注
- 使用
@inject
后,無法通過組件的refs
屬性調用其對應的方法?
if (this.tabIndex == "0") {
// 加了@inject+@observer
this.biddingRef.current.wrappedInstance.openFilter();
} else if (this.tabIndex == "1") {
// 只有@observer
this.zdRef.current.openFilter();
}
參考鏈接
// https://stackoverflow.com/questions/43847401/reactnative-mobx-how-to-access-component-refs-from-mobx
mobx
如何自動保存數據
import { observable, action, autorun, toJS, set } from "mobx";
function autoSave(store, save) {
let firstRun = true;
autorun(() => {
// 此代碼將在每次運行任何可觀察屬性時運行
// 對store進行更新。
const json = JSON.stringify(toJS(store));
if (!firstRun) {
save(json);
}
firstRun = false;
});
}
class RouteState {
@observable state = {};
constructor() {
this.load();
autoSave(this, this.save.bind(this));
}
load() {
const storeTemp = sessionStorage.getItem("route_state");
if (storeTemp) {
const data = JSON.parse(storeTemp);
set(this, data);
}
}
save(json) {
sessionStorage.setItem("route_state", json);
}
@action.bound
actionState(_state) {
this.state = _state;
}
}
// 參考鏈接
https://stackoverflow.com/questions/40292677/how-to-save-mobx-state-in-sessionstorage
場景模擬
列表A中有一個倒計時,這個倒計時會改變dataList
中item
里面的時間戳.
列表所在的頁面有個篩選浮層,篩選浮層中有DatePicker
組件
遇到的問題
DatePicker
選擇其他時間1s后,時間會重置為初始設置的時間.
原因
列表A中的倒計時每秒都在改變dataList
,這導致該頁面的子組件每秒都會執行render
函數.導致DatePicker
中的時間重置為初始時間.
解決辦法
// 新建一個包含組件.注意,這里不能用@observer將其包裹起來,
// 包含組件應該是個干凈的,由你控制是否重新渲染的組件
class FilterPopContains extends Component {
shouldComponentUpdate(nextProps, nextState) {
// 在包含組件的shouldComponentUpdate方法中自己判斷是否要執行render函數
if (JSON.stringify(nextProps) != JSON.stringify(this.props)) return true;
return false;
}
render() {
// 將props原封不動傳遞給邏輯組件
return <FilterPop {...this.props} />;
}
}
// 這個是具體的內容組件,里面有你的邏輯
@observer
class BiddingFilterPop extends Component {
render() {
return <div>
<DatePicker/>
</div>
}
}
mobx
的使用非常靈活。可以多種使用方式在同一個項目使用。並不沖突。- 推薦所有的組件都
observer
化,這並不會造成性能損耗,反而會優化組件。 - 想不到了。想起來再更新吧。