摘要:關心性能的情況下,需要手動設置這時就需要引入狀態管理庫。現在常用的狀態管理庫有和,本文會重點介紹,然后會將和進行對比,最后展望下未來的狀態管理方面趨勢。如果在任何地方都修改可觀察數據,將導致頁面狀態難以管理。
React 是一個專注于視圖層的庫。React 維護了狀態到視圖的映射關系,開發者只需關心狀態即可,由 React 來操控視圖。
在小型應用中,多帶帶使用 React 是沒什么問題的。但在復雜應用中,容易碰到一些狀態管理方面的問題,如:
React 只提供了在內部組件修改狀態的接口 setState。導致數據、業務邏輯和視圖層耦合在組件內部,不利于擴展和維護。
React 應用即一顆組件樹。兄弟節點,或者不在同一樹杈的節點之間的狀態同步是非常麻煩。
關心性能的情況下,需要手動設置 shouldComponentUpdate
這時就需要引入狀態管理庫。現在常用的狀態管理庫有 Mobx 和 Redux,本文會重點介紹 Mobx,然后會將 Mobx 和 Redux 進行對比,最后展望下未來的 React 狀態管理方面趨勢。
Mobx 簡介Mobx 的理念非常簡單,可以用一個 demo 就把其核心原理說清楚。Mobx/MobxReact 中有三個核心概念,observable、observer、action。為了簡單起見,本文沒有提及 computed 等概念。
observable: 通過 observable(state) 定義組件的狀態,包裝后的狀態是一個可觀察數據(Observable Data)。
observer: 通過 observer(ReactComponent) 定義組件。
action: 通過 action 來修改狀態。
簡化圖如下:
只講概念還比較模糊,下面給大家舉個例子。
點擊運行 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); returnHello,{user.name}!} }) ReactDOM.render(, document.getElementById("mount")); // 非視圖層事件觸發,外部直接觸發 action changeName("Wang2")// render Wang2 // 重點:沒有觸發重新渲染 // 原因:Hello 組件并沒有用到 `user.age` 這個可觀察數據 changeAge("18") // no console
例子看完了,是不是非常簡單。
使用 Mobx,組件狀態可以在外部定義(也可以在組件內部),因此,數據、業務邏輯可以輕易地和視圖層分離,提高應用的可擴展性和可維護性。另外,由于組件狀態可以在外部定義,兄弟節點之間的狀態同步也非常容易。最后一點, Mobx 知道什么時候應該渲染頁面,因此基本不需要手動設置 shouldComponentUpdate 來提高應用性能。
接下來給大家介紹下 Mobx 中 observable observer action 的用法,并會簡單介紹一下其原理。
observableMobx 如此簡單的原因之一,就是使用了可觀察數據(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 也會進行攔截,并通知依賴它的組件重新渲染。
observerobserver 接收一個 React 組件作為參數,并將其轉變成響應式(Reactive)組件。
// 普通組件 const Hello = mobxReact.observer(class Hello extends React.Component { render() { returnHello,{user.name}!} }) // 函數組件 const Hello = mobxReact.observer( () => (Hello,{user.name}!))
響應式組件,即當且僅當組件依賴的可觀察數據發生改變時,組件才會自動響應,并重新渲染。
在本文最開始的例子中,響應式組件依賴了 user.name,但是沒有依賴 user.age。所以當user.name 發現變化時,組件更新。而 user.age 發生變化時,組件沒有更新。
這里再詳細分析本文中的第一個例子:
user.name = "Wang2"http:// 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。
action在 Mobx 中是可以直接修改可觀察數據,來進行更新組件的,但不建議這樣做。如果在任何地方都修改可觀察數據,將導致頁面狀態難以管理。
所有對可觀察數據地修改,都應該在 action 中進行。
const changeName = mobx.action(name => user.name = name)
使用 Mobx 可以將組件狀態定義在組件外部,這樣,組件邏輯和組件視圖便很容易分離,兄弟組件之間的狀態也很容易同步。另外,也不再需要手動使用 shouldComponentUpdate 進行性能優化了。
Mobx 與 Redux 對比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 使用起來更加簡單,更適合現在以業務驅動、快速迭代的開發節奏。
展望: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 比較新,業內的實踐非常少,如果不是急需,現在還可以先觀望一下。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/83894.html
摘要:我現在寫的這些是為了解決和這兩個狀態管理庫之間的困惑。這甚至是危險的,因為這部分人將無法體驗和這些庫所要解決的問題。這肯定是要第一時間解決的問題。函數式編程是不斷上升的范式,但對于大部分開發者來說是新奇的。規模持續增長的應 原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用...
摘要:前言當項目越發復雜時,我們發現僅僅是提升狀態已經無法適應如此復雜的狀態管理了,程序狀態變得比較難同步,操作,到處是回調,發布,訂閱,這意味著我們需要更好的狀態管理方式,于是就引入了狀態管理庫,如,,,等。 前言 當項目越發復雜時,我們發現僅僅是提升狀態已經無法適應如此復雜的狀態管理了,程序狀態變得比較難同步,操作,到處是回調,發布,訂閱,這意味著我們需要更好的狀態管理方式,于是就引入了...
摘要:一關于狀態管理它們都有統一維護管理應用狀態的能力某一狀態只有一個可信數據來源通常命名為,指狀態容器操作更新狀態方式統一,并且可控通常以方式提供更新狀態的途徑支持將與組件連接,如,通常使用狀態管理庫后,我們將組件從業務上劃分為兩類容器組件 一. 關于狀態管理 它們都有統一維護管理應用狀態的能力; 某一狀態只有一個可信數據來源(通常命名為store,指狀態容器); 操作更新狀態方式統一,并...
摘要:是一個的簡單可擴展的狀態管理庫。它的副作用是自動更新。該函數返回一個值,當返回值為的時候,才會繼續觸發第一個函數。當返回值為時,不再繼續監聽。包含一個,該值用來區分執行事件的類型。 mobx 能干什么 使用 react 寫小型應用,數據、業務邏輯和視圖的模塊劃分不是很細是沒有問題的。在這個階段,引入任何狀態管理庫,都算是奢侈的。但是隨著頁面邏輯的復雜度提升,在中大型應用中,數據、業務邏...
摘要:前言現在最熱門的前端框架,毫無疑問是。對于小型應用,引入狀態管理庫是奢侈的。但對于復雜的中大型應用,引入狀態管理庫是必要的。現在熱門的狀態管理解決方案,相繼進入開發者的視野。獲得計算得到的新并返回。 前言 現在最熱門的前端框架,毫無疑問是React。 React是一個狀態機,由開始的初始狀態,通過與用戶的互動,導致狀態變化,從而重新渲染UI。 對于小型應用,引入狀態管理庫是奢侈的。 但...
閱讀 1281·2021-11-23 09:51
閱讀 1639·2021-11-16 11:45
閱讀 4076·2021-10-09 09:43
閱讀 2701·2021-07-22 16:47
閱讀 961·2019-08-27 10:55
閱讀 3461·2019-08-26 17:40
閱讀 3100·2019-08-26 11:39
閱讀 3241·2019-08-23 18:39