mobx 在ReactJS專案中的運用
mobx 能幹什麼
使用 react 寫小型應用,資料、業務邏輯和檢視的模組劃分不是很細是沒有問題的。在這個階段,引入任何狀態管理庫,都算是奢侈的。但是隨著頁面邏輯的複雜度提升,在中大型應用中,資料、業務邏輯和檢視,如果不能很好的劃分,就很有可能出現維護難、效能低下的問題。
業內比較成熟的解決方案有 redux,但是 redux 使用過程中,感覺較複雜和繁瑣。那麼為什麼不簡單一點呢?mobx 的核心理念是 簡單、可擴充套件的狀態管理庫。這可能正是你想要的。
react 關注的狀態(state)到檢視(view)的問題。而 mobx 關注的是狀態倉庫(store)到的狀態(state)的問題。
核心的概念1
mobx 最最核心的概念只有2個。 @observable 和 @observer ,它們分別對應的是被觀察者和觀察者。這是大家常見的觀察者模式,不過這裡使用了,ES7 中的 裝飾器。
使用 @observable 可以觀察類的值。
這裡使用 @observable 將 Store 的 todos 變為一個被觀察的值。
observable
倉庫
// 這裡引入的是 mobx
import {observable} from 'mobx';
class Store {
@observable todos = [{
title: "todo標題" ,
done: false,
}];
}
observer
mobx 元件
然後再使用 @observer ,將元件變為觀察者,響應 todos 狀態變化。
當狀態變化時,元件也會做相應的更新。
// 這裡引入的是 mobx-react
import {observer} from 'mobx-react';
@observer
class TodoBox extends Component {
render() {
return (
<ul>
{this.props.store.todos.map(todo => <li>{todo.title}</li>)}
</ul>
)
}
}
完整的 demo 如下。
import React, {Component} from 'react';
import { render } from 'react-dom';
import {observable} from 'mobx';
import {observer} from 'mobx-react';
// 最簡單的 mobx 就是一個觀察者模式
class Store {
// 被觀察者
@observable todos = [{
title: "完成 Mobx 翻譯",
done: false,
}];
}
// 觀察者
@observer
class TodoBox extends Component {
render() {
return (
<ul>
{this.props.store.todos.map(todo => <li>{todo.title}</li>)}
</ul>
)
}
}
const store = new Store();
render(
<TodoBox store={store} />,
document.getElementById('root')
);
通過以上的簡單的例子,展現了 mobx 分離資料、檢視的能力。
核心概念2
這一小節要介紹的兩個概念雖然也是核心概念,但是是可選的。
前面例子,只講了狀態的讀取,那麼狀態應該如何寫入呢?
答案是直接寫入!
@observer
class TodoBox extends Component {
render() {
return (
<div>
<ul>
{this.props.store.todos.map(
(todo,index) => <li key={index}>{todo.title}</li>
)}
</ul>
<div>
<input type="button" onClick={() => {
// 直接修改倉庫中的狀態值
this.props.store.todos[0].title = "修改後的todo標題"
}} value="點我"/>
</div>
</div>
)
}
}
細心的朋友一定發現了奇怪的地方,react 官方說過 props 值不能直接修改,但是引入 mobx 後 props 可以直接修改了,這太奇怪了!
解決辦法就是 mobx 的下一個概念 action。
actions
首先在 Store 中,定義一個 action。
class Store {
@observable todos = [{
title: "todo標題",
done: false,
}];
@action changeTodoTitle({index,title}){
this.todos[index].title = title
}
}
在 Component 中呼叫,這樣通過 action 的方法,就避免了直接修改 props 的問題。
<input type="button" onClick={() => {
this.props.store.changeTodoTitle({index:0,title:"修改後的todo標題"});
}} value="點我"/>
可以通過引入 mobx 定義的嚴格模式,強制使用 action 來修改狀態。
import {useStrict} from 'mobx';
useStrict(true);
computed values
在有些時候,state 並不一定是我們需要的最終資料。例如,所有的 todo 都放在 store.todos 中,而已經完成的 todos 的值(store.unfinishedTodos),可以由 store.todos 衍生而來。
對此,mobx 提供了 computed 裝飾器,用於獲取由基礎 state 衍生出來的值。如果基礎值沒有變,獲取衍生值時就會走快取,這樣就不會引起虛擬 DOM 的重新渲染。
通過 @computed + getter 函式來定義衍生值(computed values)。
import { computed } from 'mobx';
class Store {
@observable todos = [{
title: "todo標題",
done: false,
},{
title: "已經完成 todo 的標題",
done: true,
}];
@action changeTodoTitle({index,title}){
this.todos[index].title = title
}
@computed get finishedTodos () {
return this.todos.filter((todo) => todo.done)
}
}
mobx 有一套機制,如果衍生值(computed values)所依賴的基礎狀態(state)沒有發生改變,獲取衍生值時,不會重新計算,而是走的快取。因此 mobx 不會引起過度渲染,從而保障了效能。
當渲染的值為 finishedTodos ,點選修改標題,不會在控制檯列印 “render”;
換成 todos,就會列印 “render”.
這是由於已完成的 todos 值沒有改變,所以不會重新計算,而是走的快取。因此不會呼叫 render 方法。
完整 demo 如下
import React, {Component} from 'react';
import { render } from 'react-dom';
import {observable, action, computed,useStrict} from 'mobx';
import {observer} from 'mobx-react';
useStrict(true);
class Store {
@observable todos = [{
title: "todo標題",
done: false,
},{
title: "已經完成 todo 的標題",
done: true,
}];
@action changeTodoTitle({index,title}){
this.todos[index].title = title
}
@computed get unfinishedTodos () {
return this.todos.filter((todo) => todo.done)
}
}
@observer
class TodoBox extends Component {
render() {
console.log('render');
return (
<div>
<ul>
{ /* 把 unfinishedTodos 換成 todos,點選修改標題就會在控制檯列印 "render".*/ }
{this.props.store.unfinishedTodos.map(
(todo,index) => <li key={index}>{todo.title}</li>
)}
</ul>
<div>
<input type="button" onClick={() => {
this.props.store.changeTodoTitle({index:0,title:"修改後的todo標題"});
}} value="修改標題"/>
</div>
</div>
)
}
}
const store = new Store();
render(
<TodoBox store={store} />,
document.getElementById('root')
);
小結
翻譯了官網的一段文章,就拿過來做小結了。
mobx 是一個的簡單、可擴充套件的狀態管理庫。它背後的哲學非常簡單:
應用程式 state 是最基礎的資料。任何可以從 state 中衍生出來的資料,都應該自動的被衍生出。
actions 是唯一能夠改變 state 的方法。
state 是最基礎的資料,它不應該包含冗餘的和派生的資料。
computed values 派生值是通過純函式從 state 中派生而來的。當派生值依賴的狀態發生變化了,Mobx 將會自動更新派生值。如果依賴的狀態沒有改變,mobx 會做優化處理。
reactions 也是派生資料,是從 state 中派生而來的。它的副作用是自動更新 UI。(注:mobx 有一個 reaction 介面,當 state 改變時,就會呼叫它的回撥。UI 是通過 reaction 更新的。)
React 和 MobX 是非常強大的組合。React 提供了將應用狀態對映為可渲染的元件樹的機制。MobX 提供儲存和更新應用狀態的機制,供 React 使用。
React 和 MobX 提供了開發過程中常見問題的解決方案。 React 通過使用虛擬 DOM,減少了對瀏覽器 DOM 的操作。MobX 通過使用了響應式虛擬依賴狀態圖(reactive virtual dependency state graph) ,提供了應用程式狀態與 React 元件同步的機制,這樣 state 只會在需要時更新才會更新。(譯者注:這段有點難理解,大概的意思是 Mobx 關注的是 store 到 state 的過程,React 關注的是 state 到 view 的過程)。