Mobx與Redux的異同
Mobx
與Redux
都是用來管理JavaScript
應用的狀態的解決方案,用以提供在某個地方保存狀態、修改狀態和更新狀態,使我們的應用在狀態與組件上解耦,我們可以從一個地方獲得狀態,在另一個地方修改,在其他地方得到他們更新後的狀態。他們都遵循單一數據源的原則,這讓我們更容易推斷狀態的值和狀態的修改。當然他們並不一定要跟React
綁定在一起,它們也可以在AngularJs
和VueJs
這些框架庫裏使用。
描述
Redux
作者說過,如果你不知道是否需要Redux
,那就是不需要。在判斷是否需要使用Mobx
與Redux
之前,我們首先需要知道他們究竟是要解決什麼問題,以及當前是否遇到了這個問題。如今前端通常是要用組件components
來構建一個應用,而組件中通常有自己的內部狀態即state
,但是隨着應用越來越膨脹,組件自己內部維護的狀態在膨脹的應用中很快會變得混亂。隨着應用功能的不斷拓展,通常會出現一些問題:
- 一個組件通常需要和另一個組件共享狀態。
- 一個組件需要改變另一個組件的狀態。
- 組件層級太深,需要共享狀態時狀態要層層傳遞。
- 子組件更新一個狀態,可能有多個父組件,兄弟組件共用,實現困難。
這種情況下繼續使用提取狀態到父組件的方法你會發現很複雜,而且隨着組件增多,嵌套層級加深,這個複雜度也越來越高。因爲關聯的狀態多,傳遞複雜,很容易出現像某個組件莫名其妙的更新或者不更新的情況,異常排查也會困難重重。也就是說當應用膨脹到一定程度時,推算應用的狀態將會變得越來越困難,此時整個應用就會變成一個有很多狀態對象並且在組件層級上互相修改狀態的混亂應用。在很多情況下,狀態對象和狀態的修改並沒有必要綁定在一些組件上,我們可以嘗試將其提升,通過組件樹來得到與修改狀態。
目前通常的解決方案是引入狀態管理庫,比如Mobx
或Redux
,Mobx
與Redux
都是用來管理JavaScript
應用的狀態的解決方案,用以提供在某個地方保存狀態、修改狀態和更新狀態,使我們的應用在狀態與組件上解耦,我們可以從一個地方獲得狀態,在另一個地方修改,在其他地方得到他們更新後的狀態。他們都遵循單一數據源的原則,這讓我們更容易推斷狀態的值和狀態的修改。當然他們並不一定要跟React
綁定在一起,它們也可以在AngularJs
和VueJs
這些框架庫裏使用。
像Redux
和Mobx
這類狀態管理庫一般都有附帶的工具,例如在React
中使用的有react-redux
和mobx-react
,他們使你的組件能夠獲得狀態,一般情況下,這些組件被叫做容器組件container components
,或者說的更加確切的話,就是連接組件connected components
。通常只要將組件作爲連接組件,就可以在組件層級的任何地方得到和更改狀態。
對於Mobx
與Redux
的異同這個問題,是我最近在找實習的時候遇到的,分別爲react mobx
與react redux
作簡單的示例,文中的示例代碼都在https://codesandbox.io/s/react-ts-template-forked-88t6in
中。
Mobx
MobX
是一個經過戰火洗禮的庫,他通過透明的函數響應式編程transparently applying functional reactive programming - TFRP
使得狀態管理變得簡單和可擴展。MobX
背後的哲學很簡單: 任何源自應用狀態的東西都應該自動地獲得,其中包括UI
、數據序列化等等,核心重點就是: MobX
通過響應式編程實現簡單高效,可擴展的狀態管理。
// src/mobx-store/store.ts
import { observable, action, makeAutoObservable } from "mobx";
class Store {
constructor() {
makeAutoObservable(this);
}
@observable
state = {
count: 1
};
@action
setCount = (value: number) => {
this.state.count = value;
};
@action
setCountIncrement = () => {
this.state.count++;
};
}
export default new Store();
// src/counter-mobx.tsx
import React from "react";
import { observer } from "mobx-react";
import store from "./mobx-store/store";
const CountMobx: React.FC = () => {
return (
<div>
<div>{store.state.count}</div>
<button onClick={() => store.setCount(1)}>Set Count value 1</button>
<button onClick={store.setCountIncrement}>Set Count Increment</button>
</div>
);
};
export default observer(CountMobx);
Redux
Redux
用一個單獨的常量狀態樹或者叫作對象保存這一整個應用的狀態,這個對象不能直接被改變,當一些數據變化了,一個新的對象就會被創建,嚴格的單向數據流是Redux
架構的設計核心。
// src/redux-store/store.ts
import { createStore } from "redux";
const defaultState: State = {
count: 1
};
export const actions = {
SET_COUNT: "SET_COUNT" as const,
SET_COUNT_INCREMENT: "SET_COUNT_INCREMENT" as const
};
const reducer = (state: State = defaultState, action: Actions): State => {
const { type } = action;
switch (type) {
case actions.SET_COUNT: {
return { ...state, count: action.payload };
}
case actions.SET_COUNT_INCREMENT: {
return { ...state, count: state.count + 1 };
}
default:
return state;
}
};
export const store = createStore(reducer, defaultState);
export interface State {
count: number;
}
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
type SET_COUNT_INCREMENT = {
type: typeof actions.SET_COUNT_INCREMENT;
payload: void;
};
type SET_COUNT = {
type: typeof actions.SET_COUNT;
payload: number;
};
export type Actions = SET_COUNT_INCREMENT | SET_COUNT;
// src/counter-redux.tsx
import React from "react";
import { AppDispatch, actions, State } from "./redux-store/store";
import { useSelector, useDispatch } from "react-redux";
const CountRedux: React.FC = () => {
const count = useSelector((state: State) => state.count);
const dispatch = useDispatch() as AppDispatch;
return (
<div>
<div>{count}</div>
<button onClick={() => dispatch({ type: actions.SET_COUNT, payload: 1 })}>
Set Count value 1
</button>
<button
onClick={() =>
dispatch({ type: actions.SET_COUNT_INCREMENT, payload: void 0 })
}
>
Set Count Increment
</button>
</div>
);
};
export default CountRedux;
// src/App.tsx
import React from "react";
import "./styles.css";
import CountMobx from "./counter-mobx";
import CountRedux from "./counter-redux";
import { Provider as ReduxProvider } from "react-redux";
import { store } from "./redux-store/store";
const App: React.FC = () => {
return (
<div>
<div>======Mobx======</div>
<CountMobx />
<br />
<div>======Redux======</div>
<ReduxProvider store={store}>
<CountRedux />
</ReduxProvider>
</div>
);
};
export default App;
相同點
- 爲了解決狀態管理混亂,無法有效的同步的問題,統一管理應用狀態。
- 一個狀態只有一個可信的數據源,通常是以
action
的方式提供更新狀態的途徑。 - 都帶有狀態與組件的鏈接管理庫,例如
react-redux
、mobx-react
。
不同點
函數式和麪向對象
Redux
更多的是遵循函數式編程Functional Programming, FP
思想,從數據上來說Redux
理想的是immutable
,immutable
對象是不可直接賦值的對象,它可以有效的避免錯誤賦值的問題,例如reducer
就是一個純函數,對於相同的輸入總是輸出相同的結果。Mobx
則更多從面相對象Object Oriented Programming, OOP
與響應式編程Reactive Programming
角度考慮問題,從數據上說Mobx
從始至終都是一份引用,這樣可以使的Mobx
的組件可以做到精準更新,將狀態包裝成可觀察對象,一旦狀態對象變更,就能自動獲得更新。
store管理方式
- 在
Redux
應用中通常將整個應用的state
被儲存在一棵object tree
中,並且這個object tree
只存在於唯一一個store
中。 - 在
Mobx
則通常按模塊將應用狀態劃分,在多個獨立的store
中管理。
儲存數據形式
Redux
默認以JavaScript
原生對象形式存儲數據,這也就使得Redux
需要手動追蹤所有狀態對象的變更。- 在
Mobx
使用可觀察對象,通常是使用observable
讓數據的變化可以被觀察,通過把屬性轉化成getter/setter
來實現,當數據變更時將自動觸發監聽響應。
不可變和可變
Redux
狀態對象通常是不可變的Immutable
,複製代碼我們不能直接操作狀態對象,而總是在原來狀態對象基礎上返回一個新的狀態對象。Mobx
狀態對象通常是可變的Mutable
,可以直接使用新值更新狀態對象。
狀態調試
Redux
提供進行時間回溯的開發工具,同時純函數以及更少的抽象,讓調試變得更加容易。Mobx
中有更多的抽象和封裝,調試會相對比較困難,同時結果也相對難以預測。
最後
Mobx
與Redux
都是非常棒的兩個庫,使用上沒有對錯,只有合適不合適,只是可能需要在使用之前做好調研工作。或許有人需要減少編寫的代碼行數,那麼就可能會提到Redux
有太多的樣板代碼,而應該使用Mobx
,可以減少xxx
行代碼。又或許有人需要更加明確的處理對象的變更,那麼就可能感覺放棄Mobx
的響應式魔法,而使用Redux
去通過純 JavaScript
來推斷與調試。又或許兩個狀態管理庫並不衝突,可以同時存在,分別管理不同的模塊的狀態。
每日一題
https://github.com/WindrunnerMax/EveryDay
參考
https://cn.mobx.js.org/
https://www.redux.org.cn/docs/react-redux/
https://juejin.cn/post/6844903977553756168
https://juejin.cn/post/6924572729886638088
https://segmentfault.com/a/1190000011148981
https://www.cnblogs.com/tommymarc/p/15768138.html
https://blog.csdn.net/leelxp/article/details/108450518
https://blog.csdn.net/Ed7zgeE9X/article/details/121896197
https://yangleiup.github.io/accumulate/redux%E4%B8%8Emobx%E5%8C%BA%E5%88%AB.html
https://medium.com/@pie6k/better-way-to-create-type-safe-redux-actions-and-reducers-with-typescript-45386808c103