React 是一個專一於視圖層的庫。React 維護了狀態到視圖的映射關係,開發者只需關心狀態便可,由 React 來操控視圖。javascript
在小型應用中,單獨使用 React 是沒什麼問題的。但在複雜應用中,容易碰到一些狀態管理方面的問題,如:前端
React 只提供了在內部組件修改狀態的接口 setState
。致使數據、業務邏輯和視圖層耦合在組件內部,不利於擴展和維護。java
React 應用即一顆組件樹。兄弟節點,或者不在同一樹杈的節點之間的狀態同步是很是麻煩。node
關心性能的狀況下,須要手動設置 shouldComponentUpdate
react
這時就須要引入狀態管理庫。如今經常使用的狀態管理庫有 Mobx 和 Redux,本文會重點介紹 Mobx,而後會將 Mobx 和 Redux 進行對比,最後展望下將來的 React 狀態管理方面趨勢。git
Mobx 的理念很是簡單,能夠用一個 demo 就把其核心原理說清楚。Mobx/MobxReact 中有三個核心概念,observable
、observer
、action
。爲了簡單起見,本文沒有說起 computed
等概念。github
observable
: 經過 observable(state)
定義組件的狀態,包裝後的狀態是一個可觀察數據(Observable Data)。數組
observer
: 經過 observer(ReactComponent)
定義組件。性能優化
action
: 經過 action
來修改狀態。dom
簡化圖以下:
只講概念還比較模糊,下面給你們舉個例子。
點擊運行 https://jsfiddle.net/jhwleo/1L5jcykr/9/
// 經過 observable 定義組件的狀態 const user = mobx.observable({ name: "Jay", age: 22 }) // 經過 action 定義如何修改組件的狀態 const changeName = mobx.action(name => user.name = name) const changeAge = mobx.action(age => user.age = age) // 經過 observer 定義 ReactComponent 組件。 const Hello = mobxReact.observer(class Hello extends React.Component { componentDidMount(){ // 視圖層經過事件觸發 action changeName('Wang') // render Wang } render() { // 渲染 console.log('render',user.name); return <div>Hello,{user.name}!</div> } }) ReactDOM.render(<Hello />, document.getElementById('mount')); // 非視圖層事件觸發,外部直接觸發 action changeName('Wang2')// render Wang2 // 重點:沒有觸發從新渲染 // 緣由:Hello 組件並無用到 `user.age` 這個可觀察數據 changeAge('18') // no console
例子看完了,是否是很是簡單。
使用 Mobx,組件狀態能夠在外部定義(也能夠在組件內部),所以,數據、業務邏輯能夠輕易地和視圖層分離,提升應用的可擴展性和可維護性。另外,因爲組件狀態能夠在外部定義,兄弟節點之間的狀態同步也很是容易。最後一點, Mobx 知道何時應該渲染頁面,所以基本不須要手動設置 shouldComponentUpdate
來提升應用性能。
接下來給你們介紹下 Mobx 中 observable
observer
action
的用法,並會簡單介紹一下其原理。
Mobx 如此簡單的緣由之一,就是使用了可觀察數據(Observable Data)。簡單說,可觀察數據就是能夠觀察到數據的讀取、寫入,並進行攔截。
Mobx 提供了 observable
接口來定義可觀察數據。定義的可觀察數據,一般也是組件的狀態。該方法接收一個參數,參數能夠是原始數據類型、普通 Object、Array、或者 ES6 中的 Map 類型,返回一個 observable
類型的參數。
Array.isArray(mobx.observable([1,2,3])) === false // true mobx.isObservable(mobx.observable([1,2,3])) === true // true
注意,數組通過 observable
包裝後,就不是 Array 類型了,而是 Mobx 定義的一個特殊類型 ———— observable
類型。observable
類型,能夠經過 mobx.isObservable
來檢查。
雖然數據類型不同,可是使用方式基本和原來一致(原始數據類型除外)。
const observableArr = mobx.observable([1,2,3]); const observableObj = mobx.observable({name: 'Jay'}); const observableMap = mobx.observable(new Map([['name','Wang']])); console.log(observableArr[0]) // 1 console.log(observableObj.name) // Jay console.log(observableMap.get('name')) // Wang
可觀察數據類型的原理是,在讀取數據時,經過 getter
來攔截,在寫入數據時,經過setter
來攔截。
Object.defineProperty(o, key, { get : function(){ // 收集依賴的組件 return value; }, set : function(newValue){ // 通知依賴的組件更新 value = newValue }, });
在可觀察數據被組件讀取時,Mobx 會進行攔截,並記錄該組件和可觀察數據的依賴關係。在可觀察數據被寫入時,Mobx 也會進行攔截,並通知依賴它的組件從新渲染。
observer
接收一個 React 組件做爲參數,並將其轉變成響應式(Reactive)組件。
// 普通組件 const Hello = mobxReact.observer(class Hello extends React.Component { render() { return <div>Hello,{user.name}!</div> } }) // 函數組件 const Hello = mobxReact.observer( () => ( <div>Hello,{user.name}!</div> ))
響應式組件,即當且僅當組件依賴的可觀察數據發生改變時,組件纔會自動響應,並從新渲染。
在本文最開始的例子中,響應式組件依賴了 user.name
,可是沒有依賴 user.age
。因此當user.name
發現變化時,組件更新。而 user.age
發生變化時,組件沒有更新。
這裏再詳細分析本文中的第一個例子:
user.name = 'Wang2'// render Wang2 // 重點:沒有觸發從新渲染 // 緣由:Hello 組件並無用到 `user.age` 這個可觀察數據 user.age = '18' // no console
當可觀察數據變化時,Mobx 會調用 forceUpdate
直接更新組件。
而在傳統 React 應用中,當狀態、屬性變化後會先調用 shouldComponentUpdate
,該方法會深層對比先後狀態和屬性是否發生改變,再肯定是否更新組件。
shouldComponentUpdate
是很消耗性能的。Mobx 經過可觀察數據,精確地知道組件是否須要更新,減小了調用 shouldComponentUpdate
這一步。這是 Mobx 性能好的緣由之一。
另外須要注意的是 observer
並非 mobx
的方法,而是 mobx-react
的方法。mobx
和 mobx-react
關係如同 react
與 react-dom
。
在 Mobx 中是能夠直接修改可觀察數據,來進行更新組件的,但不建議這樣作。若是在任何地方都修改可觀察數據,將致使頁面狀態難以管理。
全部對可觀察數據地修改,都應該在 action
中進行。
const changeName = mobx.action(name => user.name = name)
使用 Mobx 能夠將組件狀態定義在組件外部,這樣,組件邏輯和組件視圖便很容易分離,兄弟組件之間的狀態也很容易同步。另外,也再也不須要手動使用 shouldComponentUpdate
進行性能優化了。
Mobx 的優點來源於可變數據(Mutable Data)和可觀察數據 (Observable Data) 。
Redux 的優點來源於不可變數據(Immutable data)。
可觀察數據的優點,在前文已經介紹過了。如今再來聊聊可變數據和不可變數據。
顧名思義,可變數據和不可變數據的區別在於,可變數據建立後能夠修改,不可變數據建立後不能夠修改。
可變數據,能夠直接修改,因此操做起來很是簡單。這使得使用 mobx 改變狀態,變得十分簡單。
不可變數據並不必定要用到 Immutable 庫。它徹底能夠是一種約定,只要建立後不修改便可。好比說,Redux 中的 state
。每次修改都會從新生成一個 newState
,而不會對原來的值進行改變。因此說 Redux 中的 state
就是不可變數據。
reducer(state, action) => newState.
不可變數據的優點在於,它可預測,可回溯。示例代碼以下:
function foo(bar) { let data = { key: 'value' }; bar(data); console.log(data.key); // 猜猜會打印什麼? }
若是是可變數據,data.key
的值可能會在 bar
函數中被改變,因此不能肯定會打印什麼值。可是若是是不可變數據,那麼就能夠確定打印值是什麼。這就是不可變數據的優點 ———— 可預測。不可變數據不會隨着時間的變化(程序的運行)而發生改變。在須要回溯的時候,直接獲取保存的值便可。
Mobx 與 Redux 技術選型的本質,是在可變數據與不可變數據之間選擇。具體業務場景的技術選型,還須要根據實際狀況進行分析,脫離業務場景討論技術選型是沒有意義的。但我我的在狀態管理的技術選型上,仍是傾向於 Mobx 的。緣由是前端與反作用打交道很是頻繁,有 Http 請求的反作用,Dom 操做的反作用等等。使用不可變數據,還必須得使用中間件對反作用封裝;在 Redux 中修改一次狀態,須要通過 Action、Dispatch、Reducer 三個步驟,代碼寫起來太囉嗦;而前端的程序以中小型程序爲主,純函數帶來的可預測性的收益,遠不及其帶的代碼複雜度所須要付出的成本。而 Mobx 使用起來更加簡單,更適合如今以業務驅動、快速迭代的開發節奏。
不可變數據和可變數據,都是對狀態的一種描述。那麼有沒有一種方案,能將一種狀態,同時用可變數據和不可變數據來描述呢?這樣就能夠同時享有兩者的優點了。(注意:當咱們說可變數據時,一般它仍是可觀察數據,後文統一隻說可變數據。)
答案是確定的,它就是 MST(mobx-state-tree) https://github.com/mobxjs/mob...。
MST 是一個狀態容器:一種狀態,同時包含了可變數據、不可變數據兩種不一樣的形式。
爲了讓狀態能夠在可變數據和不可變數據兩種形式之間可以高效地相互轉化,必須遵循 MST 定義狀態的方法。
在 MST 中,定義狀態必須先定義它的結構。狀態的結構是一顆樹(tree),樹是由多層模型(model)組成,model 是由多個節點組成。
在下面的代碼中,樹只有一層 model,該 model 也只有一個節點:title。title 的類型是事先定好的,在這裏是 types.string
。樹的結構定義好後,經過 create
方法傳入數據,就生成樹。
import {types} from "mobx-state-tree" // declaring the shape of a node with the type `Todo` const Todo = types.model({ title: types.string }) // creating a tree based on the "Todo" type, with initial data: const coffeeTodo = Todo.create({ title: "Get coffee" })
在一些稍微複雜的例子中,樹的 model 能夠有多層,每層能夠有多個節點,有些節點定義的是數據類型(types.xxx
),有些節點直接定義的是數據。下面的示例中,就是定義了一個多層多節點的樹。除此以外,注意 types.model
函數的第一個參數定義的是 model 的名字,第二參數定義的是 model 的全部屬性,第三個參數定義的是 action。
import { types, onSnapshot } from "mobx-state-tree" const Todo = types.model("Todo", { title: types.string, done: false }, { toggle() { this.done = !this.done } }) const Store = types.model("Store", { todos: types.array(Todo) }) // create an instance from a snapshot const store = Store.create({ todos: [{ title: "Get coffee" }]})
最關鍵的來了,請看下面的代碼。
// listen to new snapshots onSnapshot(store, (snapshot) => { console.dir(snapshot) }) // invoke action that modifies the tree store.todos[0].toggle() // prints: `{ todos: [{ title: "Get coffee", done: true }]}`
在上述代碼的第一部分,使用 onSnapshot
監聽狀態的改變。第二部分,調用 store.todos[0].toggle()
,在這個 action
中經過使用可變數據的方式,直接修改了當前的狀態。同時在 onSnapshot
生成了一個狀態快照。這個狀態快照就是狀態的不可變數據的表現形式。
MST 這麼神奇,那麼具體怎麼用呢?MST 只是一個狀態容器,同時包含了可變數據和不可變數據。你能夠用 MST 直接搭配 React 使用。能夠 MST + Mobx + React 配合着用,還能夠 MST + Redux + React 混搭着用。
MST 比較新,業內的實踐很是少,若是不是急需,如今還能夠先觀望一下。