使用 React&Mobx 的幾個(gè)最佳實(shí)踐

Mobx 是我非常喜歡的 React 狀態(tài)管理庫,它非常靈活,同時(shí)它的靈活也會給開發(fā)帶來非常多的問題,因此我們在開發(fā)的時(shí)候也要遵循一些寫法上的最佳實(shí)踐,使我們的程序達(dá)到最好的效果。
在 store 中維護(hù)業(yè)務(wù)邏輯
盡量不要把業(yè)務(wù)邏輯寫在?React Component?里面。當(dāng)你把業(yè)務(wù)邏輯寫在組件里面的時(shí)候,很難及時(shí)定位錯(cuò)誤的,因?yàn)闃I(yè)務(wù)邏輯分散在各種不同的組件里面,讓你很難來通過行為來定義到底是哪些代碼涉及的這個(gè)錯(cuò)誤,不同組件復(fù)用這些邏輯也很困難。
最好在?stores?中把業(yè)務(wù)邏輯編寫成方法,并在你的?Component?中調(diào)用這些方法。
只允許在 store 中修改屬性
盡量不要在一個(gè)?Component?里直接修改一個(gè)?store?的屬性。只有?store?本身可以修改他自己的屬性。
當(dāng)你要改變屬性的時(shí)候,請調(diào)用相應(yīng)的?store?方法。不然的話你的屬性修改會散落在各處不受控制,這是很難調(diào)試的。
class?Store?{
??@observable?text;
??
[email protected]
??handleSearch?=?value?=>?{
????this.text?=?value
??}
}
const?store?=?new?Store();
@observer
class?Home?extends?Component?{
??
??handleChanged?=?(event)?=>?{
????store.handleSearch(event.target.value);
??}
??
??render()?{
????return?(
??????<input
????????value={store.searchText}
????????onChange={this.handleChanged}
??????/>
????);
??}
}
所有屬性更改都用 action
使用?action?后,可以清楚的看出哪些代碼可以更改可觀察的變量,并且方便調(diào)試工具給出更多的信息
使用?transaction?可以將多個(gè)應(yīng)用狀態(tài)(Observable)的更新視為一次操作,并只觸發(fā)一次監(jiān)聽者(Reactions)的動作(UI更新、網(wǎng)絡(luò)請求等),避免多次重復(fù)渲染。action中封裝了transaction,多次改變@observable變量時(shí),只會重新渲染一次,提高了性能。
class?Store?{
??@observable?name;
??@observable?age;
??@action
??change(name,age){
????this.name?=?name;
????this.age?=?age;
??}
}
從 store 中分離出 API 請求
不要在你的?store?里調(diào)用?API?接口,這會讓它們很難測試,也讓代碼變的更復(fù)雜和耦合。額外建一個(gè)類,把?API?接口調(diào)用放進(jìn)去,并在?store?的構(gòu)造函數(shù)里實(shí)例化他們來使用。當(dāng)你編寫測試代碼時(shí),你可以很容易地模擬這些?api?并把你的模擬?api?實(shí)例傳給每一個(gè)?store。
class?UserService?{
??fetchUser?=?()?=>?axios.get('/api/user')
}
class?Store?{
??@observable?todos?=?[];
??constructor(userService)?{
????this.userService?=?userService;
??}
??fetchUser?=?async?()?=>?{
????const?todos?=?await?this.userService.fetchUser();
????runInAction(()?=>?{
??????this.todos?=?todos;
????});
??}
}
const?userService?=?new?UserService();
const?store?=?new?Store(userService);
對每一個(gè) component 都聲明 @observer
@observer?可以用來將?React?組件轉(zhuǎn)變成響應(yīng)式組件。它用?mobx.autorun?包裝了組件的?render?函數(shù)以確保任何組件渲染中使用的數(shù)據(jù)變化時(shí)都可以強(qiáng)制刷新組件。
給每一個(gè)?component?都標(biāo)注?@observer?,這可以使得他們可以隨著?store prop?的改變而更新。如果子組件沒有標(biāo)注?@observer?的話,就會導(dǎo)致其父?component?(有?@observer?)刷新。因此我們要盡可能的讓每個(gè)子?component?都標(biāo)注?@observer?,這可以減少不必要的重新渲染。
不要緩存 observables 屬性
Observer?組件只會追蹤在?render?方法中存取的數(shù)據(jù)。如果你從?observable?屬性中提取數(shù)據(jù)并將其緩存在組件里,這樣的數(shù)據(jù)是不會被追蹤的:
class?Store?{
??@observable?name;
??@observable?age;
}
class?Home?extends?React.Component?{
??componentWillMount()?{
????//?錯(cuò)誤的,info?的更新不會被追蹤
????this.info?=?store.name?+?store.age
??}
??render()?{
????return?<div>{this.info}div>
??}
}
使用 @computed
比如剛剛的例子,使用?@computed?屬性來處理一些涉及多個(gè)屬性的邏輯。使用?@computed?可以減少這樣的判斷類業(yè)務(wù)邏輯在組件里面出現(xiàn)的頻率。
class?Store?{
??@observable?name;
??@observable?age;
??@computed?info?=?()?=>?{
????return?this.name?+?this.age;
??}
}
class?Home?extends?React.Component?{
??render()?{
????return?<div>{store.info}div>
??}
}
多編寫受控組件
多編寫可控組件,這樣會大大降低你的測試復(fù)雜度,也讓你的組件易于管理。
當(dāng)需要追蹤對象屬性時(shí)、使用 map
MobX?可以做許多事,但是它無法將原始類型值轉(zhuǎn)變成?observable?(盡管可以用對象來包裝它們)。所以說值不是?observable,而對象的屬性才是。這意味著?@observer?實(shí)際上是對間接引用值作出反應(yīng)。所以如果像下面這樣初始化的話,Timer?組件是不會作出任何反應(yīng)的:
ReactDom.render(<Timer?timerData={timerData.secondsPassed}?/>,?document.body)
在這行代碼中,只是?secondsPassed?的當(dāng)前值傳遞給了?Timer,這個(gè)值是不可變值 (JS中的所有原始類型值都是不可變的)。這個(gè)值永遠(yuǎn)都不會改變,所以?Timer?也永遠(yuǎn)不會更新。?secondsPassed?屬性將來會改變,所以我們需要在組件內(nèi)訪問它。或者換句話說: 永遠(yuǎn)只傳遞擁有?observable?屬性的對象。
如果你想追蹤對象中每個(gè)屬性的變更,可以使用?map:
observable.map(values?)?創(chuàng)建一個(gè)動態(tài)鍵的?observable?映射。如果你不但想對一個(gè)特定項(xiàng)的更改做出反應(yīng),而且對添加或刪除該項(xiàng)也做出反應(yīng)的話,那么?observable?映射會非常有用。
class?Store?{
[email protected]({secondsPassed:0});
[email protected]
??change(value){
????this.timerData.set('secondsPassed',value);
??}
}
class?Home?extends?React.Component?{
??render()?{
????return?<div>{store.timerData.get('secondsPassed')}div>
??}
}推薦閱讀
