MobX
MobX通過透明的函數響應式編程,使得其成爲一種簡單、可擴展的狀態管理工具。使用MobX可以將應用編程響應式的編程方式。
其背後的原理是:任何源自應用狀態的東⻄都應該自動地獲得。
React和MobX是一對強力組合。React通過提高機制把應用狀態轉換爲可渲染組件樹並對其進行渲染;MobX提供機制來存儲和更新應用狀態供React使用。
1. 概念
MobX主要區分了概念:
- State(狀態)
- Derivations(衍生)
- Actions(動作)。
在Derivations中又可以再細分爲Computed values(計算值)與Reactions(反應)。
-
State(狀態)
狀態是驅動應用的數據。這其中包含對象,數組等。
-
Derivations(衍生)
任何源自狀態並且不會再有任何進一步的相互作用的東西就是衍生
衍生以多種形式存在:
- 用戶界面
- 衍生數據,比如剩下的待辦事項的數量。
- 後端集成,比如把變化發送到服務器端。
MobX還區分了兩種類型的衍生:
- Computed values(計算值):它們是永遠可以使用純函數(pure function)從當前可觀察狀態中衍生出的值。
- Reactions(反應):Reactions 是當狀態改變時需要自動發生的副作用。需要有一個橋樑來連接命令式編程(imperative programming)和響應式編程(reactive programming)。或者說得更明確一些,它們最終都需要實現I / O 操作。
黃金法則: 如果你想創建一個基於當前狀態的值時,請使用
computed
。 -
Actions(動作)
動作是任一一段可以改變狀態的代碼。例如用戶事件、後端數據推送、預定事件、等等。
在MobX中可以顯式地定義動作,它可以幫你把代碼組織的更清晰。如果是在嚴格模式下使用MobX的話,MobX會強制只有在動作之中纔可以修改狀態。
2. 原則
MobX支持單向數據流,也就是動作改變狀態,而狀態的改變會更新所有受影響的視圖。
- 當State(狀態)改變時,所有Derivations(衍生)都會進行原子級的自動更新。因此永遠不可能觀察到中間值。
- 所有Derivations(衍生)默認都是同步更新。這意味着例如Actions(動作)可以在改變State(狀態)之後直接可以安全地檢查計算值。
- Computed values(計算值)是延遲更新的。任何不在使用State(狀態)的 Computed values(計算值)將不會更新,直到需要它進行副作用(I / O)操作時。 如果視圖不再使用,那麼它會自動被垃圾回收。
- 所有的 Computed values(計算值)都應該是純淨的。它們不應該用來改變State(狀態)。
3. API
裝飾器
裝飾器 | 說明 |
---|---|
@observable |
可以在 ES7 或者 TypeScript 類屬性中屬性使用,將其轉換成可觀察的。 @observable 可以在實例字段和屬性 getter 上使用。 |
@action |
用於裝飾可觀察變量的狀態的更新。在嚴格模式下,對於可觀察變量的狀態的更新必須使用此裝飾器 |
@observer |
用來將 React 組件轉變成響應式組件。它用 mobx.autorun 包裝了組件的 render 函數以確保任何組件渲染中使用的數據變化時都可以強制刷新組件。 |
@computed |
如果已經啓用 decorators 的話,可以在任意類屬性的 getter 上使用 @computed 裝飾器來聲明式的創建計算屬性。 |
其他
-
Autorun
當你想創建一個響應式函數,而該函數本身永遠不會有觀察者時,可以使用
mobx.autorun
。這通常是當你需要從反應式代碼橋接到命令式代碼的情況,例如打印日誌、持久化或者更新UI的代碼。
當使用
autorun
時,所提供的函數總是立即被觸發一次,然後每次它的依賴關係改變時會再次被觸發。 -
Reaction
它是
autorun
的變種,對於如何追蹤observable
賦予了更細粒度的控制。它接收兩個函數參數,第一個(數據 函數)是用來追蹤並返回數據作爲第二個函數(效果 函數)的輸入。
不同於 autorun 的是當創建時效果 函數不會直接運行,只有在數據表達式首次返回一個新值後纔會運行。 在執行 效果 函數時訪問的任何 observable 都不會被追蹤。
reaction
返回一個清理函數。用法:
reaction(() => data, (data, reaction) => { sideEffect }, options?)
-
enforceActions(嚴格模式)
在嚴格模式下,不允許在 action 外更改任何狀態。 可接收的值:
- "never" (默認): 可以在任意地方修改狀態
- "observed": 在某處觀察到的所有狀態都需要通過動作進行更改。在正式應用中推薦此嚴格模式。
- "always": 狀態始終需要通過動作來更新(實際上還包括創建)。
-
runInAction: 異步 Actions
在嚴格模式下,所有修改可觀察變量的地方必須(放在action中或)添加
@action
。但@action
只會對當前運行的函數作出反應,而不會對當前運行函數所調用的函數(不包含在當前函數之內)作出反應。這意味着如果@action
中存在setTimeout
、promise
的then
或async
語句,並且在回調函數中某些狀態改變了,那麼這些回調函數也應該包裝在@action
中。這時候我們可以使用runInAction
包裝修改可觀察變量。mobx.configure({ enforceActions: "always" }); class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @action fetchProjects() { this.githubProjects = [] this.state = "pending" fetchGithubProjectsSomehow().then( projects => { const filteredProjects = somePreprocessing(projects) // 將‘“最終的”修改放入一個異步動作中 runInAction(() => { this.githubProjects = filteredProjects this.state = "done" }) }, error => { // 過程的另一個結局:... runInAction(() => { this.state = "error" }) } ) } }
async / await
只是圍繞基於promise
過程的語法糖。 結果是@action
僅應用於代碼塊,直到第一個await
。 在每個await
之後,一個新的異步函數將啓動,所以在每個await
之後,狀態修改代碼應該被包裝成動作。 這正是runInAction
再次派上用場的地方。mobx.configure({ enforceActions: "always" }); 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" }) } } }
對比及總結
autorun
需要手動清理,@computed
會被自動清理。autorun
總是立即被觸發一次,@computed
只有當它有自己的觀察者時纔會重新計算。autorun
用於執行打印日誌、持久化或者更新UI的代碼,@computed
用於產生一個新值。- 如果你有一個函數應該自動運行,但不會產生一個新的值,請使用
autorun
。 其餘情況都應該使用@computed
。
4. 使用
安裝MobX
yarn add mobx --save
yarn add mobx-react --save
MobX推薦使用ES7的decorator語法,以實現最精煉的表達。安裝裝飾器支持:
npm i --save-dev babel-plugin-transform-decorators-legacy
然後在 .babelrc 文件中啓用它:
{
"presets": ["module:metro-react-native-babel-preset"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
[
"@babel/transform-runtime",
{
"helpers": true,
"regenerator": false
}
]
]
}
一些問題
最新的5.x以上的mobx在安裝時存在問題,這裏我自己使用的是4.x
另外對於Babel也需要使用
貼出自己的package.json
"dependencies": {
"babel-core": "^6.26.3",
"babel-preset-stage-0": "^6.24.1",
"mobx": "^4.3.1",
"mobx-react": "^5.1.0",
"react": "16.6.1",
"react-native": "0.57.7"
},
"devDependencies": {
"@babel/core": "^7.2.0",
"@babel/plugin-proposal-decorators": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/runtime": "^7.2.0",
"babel-jest": "23.6.0",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"babel-preset-react-native-stage-0": "^1.0.1",
"jest": "23.6.0",
"metro-react-native-babel-preset": "0.50.0",
"react-test-renderer": "16.6.1"
}