MobX的一些總結

MobX

MobX通過透明的函數響應式編程,使得其成爲一種簡單、可擴展的狀態管理工具。使用MobX可以將應用編程響應式的編程方式。

其背後的原理是:任何源自應用狀態的東⻄都應該自動地獲得。

React和MobX是一對強力組合。React通過提高機制把應用狀態轉換爲可渲染組件樹並對其進行渲染;MobX提供機制來存儲和更新應用狀態供React使用。

1. 概念

MobX Overview

MobX主要區分了概念:

  • State(狀態)
  • Derivations(衍生)
  • Actions(動作)。

在Derivations中又可以再細分爲Computed values(計算值)與Reactions(反應)。

  1. State(狀態)

    狀態是驅動應用的數據。這其中包含對象,數組等。

  2. Derivations(衍生)

    任何源自狀態並且不會再有任何進一步的相互作用的東西就是衍生

    衍生以多種形式存在:

    • 用戶界面
    • 衍生數據,比如剩下的待辦事項的數量。
    • 後端集成,比如把變化發送到服務器端。

    MobX還區分了兩種類型的衍生:

    • Computed values(計算值):它們是永遠可以使用純函數(pure function)從當前可觀察狀態中衍生出的值。
    • Reactions(反應):Reactions 是當狀態改變時需要自動發生的副作用。需要有一個橋樑來連接命令式編程(imperative programming)和響應式編程(reactive programming)。或者說得更明確一些,它們最終都需要實現I / O 操作。

    黃金法則: 如果你想創建一個基於當前狀態的值時,請使用computed

  3. Actions(動作)

    動作是任一一段可以改變狀態的代碼。例如用戶事件、後端數據推送、預定事件、等等。

    在MobX中可以顯式地定義動作,它可以幫你把代碼組織的更清晰。如果是在嚴格模式下使用MobX的話,MobX會強制只有在動作之中纔可以修改狀態。

2. 原則

MobX支持單向數據流,也就是動作改變狀態,而狀態的改變會更新所有受影響的視圖

State

  • 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 裝飾器來聲明式的創建計算屬性。

其他

  1. Autorun

    當你想創建一個響應式函數,而該函數本身永遠不會有觀察者時,可以使用mobx.autorun

    這通常是當你需要從反應式代碼橋接到命令式代碼的情況,例如打印日誌、持久化或者更新UI的代碼。

    當使用autorun時,所提供的函數總是立即被觸發一次,然後每次它的依賴關係改變時會再次被觸發。

  2. Reaction

    它是autorun的變種,對於如何追蹤observable賦予了更細粒度的控制。

    它接收兩個函數參數,第一個(數據 函數)是用來追蹤並返回數據作爲第二個函數(效果 函數)的輸入。

    不同於 autorun 的是當創建時效果 函數不會直接運行,只有在數據表達式首次返回一個新值後纔會運行。 在執行 效果 函數時訪問的任何 observable 都不會被追蹤。

    reaction返回一個清理函數。

    用法:

    reaction(() => data, (data, reaction) => { sideEffect }, options?)
    
  3. enforceActions(嚴格模式)

    在嚴格模式下,不允許在 action 外更改任何狀態。 可接收的值:

    • "never" (默認): 可以在任意地方修改狀態
    • "observed": 在某處觀察到的所有狀態都需要通過動作進行更改。在正式應用中推薦此嚴格模式。
    • "always": 狀態始終需要通過動作來更新(實際上還包括創建)。
  4. runInAction: 異步 Actions

    在嚴格模式下,所有修改可觀察變量的地方必須(放在action中或)添加@action。但@action只會對當前運行的函數作出反應,而不會對當前運行函數所調用的函數(不包含在當前函數之內)作出反應。這意味着如果@action中存在setTimeoutpromisethenasync語句,並且在回調函數中某些狀態改變了,那麼這些回調函數也應該包裝在@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"
                })
            }
        }
    }
    

對比及總結

  1. autorun需要手動清理,@computed會被自動清理。
  2. autorun總是立即被觸發一次,@computed只有當它有自己的觀察者時纔會重新計算。
  3. autorun用於執行打印日誌、持久化或者更新UI的代碼,@computed用於產生一個新值。
  4. 如果你有一個函數應該自動運行,但不會產生一個新的值,請使用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"
  }

參考

  1. Ten minute introduction to MobX and React
  2. MobX CN
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章