1. 程式人生 > >【譯】Redux 還是 Mobx,讓我來解決你的困惑!

【譯】Redux 還是 Mobx,讓我來解決你的困惑!

array 內容 輸出 htop ace medium 則無 面向對象語言 時間

原文地址:Redux or MobX: An attempt to dissolve the Confusion

原文作者:rwieruch

我在去年大量的使用了 Redux,但我最近都在使用 Mobx 來做狀態(state)管理。似乎現在社區裏關於該選什麽來替代 Redux 很自然地成為了一件困惑的事。開發者不確定該選擇哪種解決方案。這個問題並不只是出現在 Redux 與 Mobx 上。無論何時,只要存在選擇,人們就會好奇最好的解決問題的方式是什麽。我現在寫的這些是為了解決 Redux 和 Mobx 這兩個狀態管理庫之間的困惑。

大部分的文章都用 React 來介紹 Mobx 和 Redux 的用法。但是在大部分情況下你都可以將 React 替換成 Angular 、 Vue 或其他。

在 2016 年年初的時候我用 React + Redux 寫了一個相當大的應用。在我發現可以使用 Mobx 替代 Redux 時,我花時間將應用從 Redux 重構成了 Mobx 。現在我可以非常自在的使用它倆並且解釋它倆的用法。

這篇文章將要講什麽呢?如果你不打算看這麽長的文章(TLDR:too long, didn‘t read(查看此鏈接請自備梯子)),你可以看下目錄。但我想給你更多細節:第一,我想簡單地回顧狀態管理庫為我們解決了什麽問題。畢竟我們寫 React 時只用 setState() 或寫其他 SPA 框架時用 setState() 類似的方法一樣也可以做的不錯。第二,我會大致的說下它們之間的相同之處和不同之處。第三,我會給 React 生態初學者指明怎樣學習 React 的狀態管理。友情提醒:在你深入 Mobx 和 Redux 之前,請先使用 setState()

。最後,如果你已經有一個使用了 Mobx 或 Redux 的應用,我將會就如何從其中一個狀態管理庫重構到另一個給你更多我的理解。


目錄

  • 我們要解決的是什麽問題?
  • Mobx 和 Redux 的不同?
  • React 狀態管理的學習曲線
  • 嘗試另一個狀態管理方案?
  • 最後思考

我們要解決的是什麽問題?

所有人都想在應用中使用狀態管理。但它為我們解決了什麽問題?很多人開始一個小應用時就已經引入一個狀態管理庫。所有人都在談論 Mobx 和 Redux ,不是嗎?但大部分應用在一開始的時候並不需要大型的狀態管理。這甚至是危險的,因為這部分人將無法體驗 Mobx 和 Redux 這些庫所要解決的問題。

如今的現狀是要用組件(components)來構建一個前端應用。組件有自己的內部狀態。舉個栗子,在 React 中上述的本地狀態是用this.state

setState()來處理。但本地狀態的狀態管理在膨脹的應用中很快會變得混亂,因為:

  • 一個組件需要和另一個組件共享狀態
  • 一個組件需要改變另一個組件的狀態

到一定程度時,推算應用的狀態將會變得越來越困難。它就會變成一個有很多狀態對象並且在組件層級上互相修改狀態的混亂應用。在大部分情況下,狀態對象和狀態的修改並沒有必要綁定在一些組件上。當你把狀態提升時,它們可以通過組件樹得到。

所以,解決方案是引入狀態管理庫,比如:Mobx 或 Redux。它提供工具在某個地方保存狀態、修改狀態和更新狀態。你可以從一個地方獲得狀態,一個地方修改它,一個地方得到它的更新。它遵循單一數據源的原則。這讓我們更容易推斷狀態的值和狀態的修改,因為它們與我們的組件是解耦的。

像 Redux 和 Mobx 這類狀態管理庫一般都有附帶的工具,例如在 React 中使用的有 react-redux 和 mobx-react,它們使你的組件能夠獲得狀態。一般情況下,這些組件被叫做容器組件(container components),或者說的更加確切的話,就是連接組件( connected components )。只要你將組件升級成連接組件,你就可以在組件層級的任何地方得到和更改狀態。

Mobx 和 Redux 的不同?

在我們深入了解 Redux 和 Mobx 的不同之前,我想先談談它們之間的相同之處。

這兩個庫都是用來管理 JavaScript 應用的狀態。它們並不一定要跟 React 綁定在一起,它們也可以在 AngularJs 和 VueJs 這些其他庫裏使用。但它們與 React 的理念結合得非常好。

如果你選擇了其中一個狀態管理方案,你不會感到被它鎖定了。因為你可以在任何時候切換到另一個解決方案。你可以從 Mobx 換成 Redux 或從 Redux 換成 Mobx。我下面會展示如何能夠做到。

Dan Abramov 的 Redux 是從 flux 架構派生出來的。和 flux 不同的是,Redux 用單一 store 而不是多個 store 來保存 state,另外,它用純函數替代 dispatcher 來修改 state,如果你對 flux 不熟並且沒接觸過狀態管理,不要被這段內容所煩惱。

Redux 被 FP(函數式編程)原則所影響。FP 可以在 JavaScript 中使用,但很多人有面向對象語言的背景,比如 Java。他們在剛開始的時候很難適應函數式編程的原則。這就是為什麽對於初學者來說 Mobx 可能更加簡單。

既然 Redux 擁抱 FP,那它使用的就是純函數。一個接受輸入並返回輸出並且沒有其他依賴的純函數。一個純函數在相同的輸入下輸出總是相同而且沒有任何副作用。


(state, action) => newState

你的 Redux state 是不可變的,你應該總是返回一個新的 state 而不是修改原 state。你不應該執行 state 的修改或依據對象引用的更改。


// don't do this in Redux, because it mutates the array
function addAuthor(state, action) {
  return state.authors.push(action.author);
}

// stay immutable and always return a new object
function addAuthor(state, action) {
  return [ ...state.authors, action.author ];
}

最後,在 Redux 的習慣用法裏,state 的格式是像數據庫一樣標準化的。實體之間只靠 id 互相引用,這是最佳實踐。雖然不是每個人都這樣做,你也可以使用 normalizr 來使 state 標準化。標準化的 state 讓你能夠保持一個扁平的 state 和保持實體為單一數據源。


{
  post: {
    id: 'a',
    authorId: 'b',
    ...
  },
  author: {
    id: 'b',
    postIds: ['a', ...],
    ...
  }
}

Michel Weststrate 的 Mobx 則是受到面向對象編程和響應式編程的影響。它將 state 包裝成可觀察的對象,因此你的 state 就有了 Observable 的所有能力。state 數據可以只有普通的 setter 和 getter,但 observable 讓我們能在數據改變的時候得到更新的值。

Mobx 的 state 是可變的,所以你直接的修改 state :


function addAuthor(author) {
  this.authors.push(author);
}

除此之外,state 實體保持嵌套的數據結構來互相關聯。你不必標準化 state,而是讓它們保持嵌套。


{
  post: {
    id: 'a',
    ...
    author: {
      id: 'b',
      ...
    }
  }
}

單 store 與多 stores

在 Redux 中,你將所有的 state 都放在一個全局的 store。這個 store 對象就是你的單一數據源。另一方面,多個 reducers 允許你修改不可變的 state。

Mobx 則相反,它使用多 stores。和 Redux 的 reducers 類似,你可以在技術層面或領域進行分治。也許你想在不同的 stores 裏保存你的領域實體,但仍然保持對視圖中 state 的控制。畢竟你配置 state 是為了讓應用看起來更合理。

從技術層面來說,你一樣可以在 Redux 中使用多個 stores。沒有人強迫你只能只用一個 store。 但那不是 Redux 建議的用法。因為那違反了最佳實踐。在 Redux 中,你的單 store 通過 reducers 的全局事件來響應更新。

如何使用?

你需要跟隨下面的代碼學習使用 Redux,首先在全局 state 上新增一個 user 數組。你可以看到我通過對象擴展運算符來返回一個新對象。你同樣可以在 ES6(原文為 ES5,實際是應該是 ES6)中使用 Object.assign() 來操作不可變對象。


const initialState = {
  users: [
    {
      name: 'Dan'
    },
    {
      name: 'Michel'
    }
  ]
};

// reducer
function users(state = initialState, action) {
  switch (action.type) {
  case 'USER_ADD':
    return { ...state, users: [ ...state.users, action.user ] };
  default:
    return state;
  }
}

// action
{ type: 'USER_ADD', user: user };

你必須使用 dispatch({ type: ‘USER_ADD‘, user: user });來為全局 state 添加一個新 user 。

在 Mobx 中,一個 store 只管理一個子 state(就像 Redux 中管理子 state 的 reducer),但你可以直接修改 state 。@observable 讓我們可以觀察到 state 的變化。


class UserStore {
  @observable users = [
    {
      name: 'Dan'
    },
    {
      name: 'Michel'
    }
  ];
}

現在我們就可以調用 store 實例的方法:userStore.users.push(user);。這是一種最佳實踐,雖然使用 actions 去操作 state 的修改更加清楚明確。


class UserStore {
  @observable users = [
    {
      name: 'Dan'
    },
    {
      name: 'Michel'
    }
  ];

  @action addUser = (user) => {
    this.users.push(user);
  }
}

在 Mobx 中你可以加上 useStrict() 來強制使用 action。現在你可以調用 store 實例上的方法:userStore.addUser(user); 來修改你的 state 。

你已經看到如何在 Redux 和 Mobx 中更新 state 。它們是不同的,Redux 中 state 是只讀的,你只能使用明確的 actions 來修改 state ,Mobx 則相反,state 是可讀和寫的,你可以不使用 actions 直接修改 state,但你可以 useStrict() 來使用明確的 actions 。

React 狀態管理的學習曲線

React 應用廣泛使用 Redux 和 Mobx 。但它們是獨立的狀態管理庫,可以運用在除 React 的任何地方。它們的互操作庫讓我們能簡單的連接React 組件。Redux + React 的 react-redux 和 MobX + React 的 mobx-react 。稍後我會說明它倆如何在 React 組件樹中使用。

在最近的討論中,人們在爭論 Redux 的學習曲線。這通常發生在下面的情境中:想使用 Redux 做狀態管理的 React 初學者。大部分人認為 React 和 Redux 本身都有頗高的學習曲線,兩者結合的話會失控。一個替代的選擇就是 Mobx ,因為它更適合初學者。

然而,我會建議 React 的初學者一個學習狀態管理的新方法。先學習
React 組件內部的狀態管理功能。在 React 應用,你首先會學到生命周期方法,而且你會用 setState()this.state 解決本地的狀態管理。我非常推薦上面的學習路徑。不然你會在 React 的生態中迷失。在這條學習路徑的最後,你會認識到組件內部管理狀態難度越來越大。畢竟那是 The Road to learn React 書裏如何教授 React 狀態管理的方法。

現在我們重點討論 Redux 和 Mobx 為我們解決了什麽問題?它倆都提供了在組件外部管理應用狀態的方法。state 與組件相互解耦,組件可以讀取 state ,修改 state ,有新 state 時更新。這個 state 是單一數據源。

現在你需要選擇其中一個狀態管理庫。這肯定是要第一時間解決的問題。此外,在開發過相當大的應用之後,你應該能很自如使用 React 。

初學者用 Redux 還是 Mobx ?

一旦你對 React 組件和它內部的狀態管理熟悉了,你就能選擇出一個狀態管理庫來解決你的問題。在我兩個庫都用過後,我想說 Mobx 更適合初學者。我們剛才已經看到 Mobx 只要更少的代碼,甚至它可以用一些我們現在還不知道的魔法註解。

用 Mobx 你不需要熟悉函數式編程。像“不可變”之類的術語對你可能依然陌生。函數式編程是不斷上升的範式,但對於大部分 JavaScript 開發者來說是新奇的。雖然它有清晰的趨勢,但並非所有人都有函數式編程的背景,有面向對象背景的開發者可能會更加容易適應 Mobx 的原則。

註:Mobx 可以很好的在 React 內部組件狀態管理中代替 setState,我還是建議繼續使用 setState() 管理內部狀態。但鏈接文章很清楚的說明了在 React 中用 Mobx 完成內部狀態管理是很容易的。

規模持續增長的應用

在 Mobx 中你改變註解過的對象,組件就會更新。Mobx 比 Redux 使用了更多的內部魔法實現,因此在剛開始的時候只要更少的代碼。有 Angular 背景的會覺得跟雙向綁定很像。你在一個地方保存 state ,通過註解觀察 state ,一旦 state 修改組件會自動的更新。

Mobx 允許直接在組件樹上直接修改 state 。


// component
<button onClick={() => store.users.push(user)} />

更好的方式是用 store 的 @action


// component
<button onClick={() => store.addUser(user)} />

// store
@action addUser = (user) => {
  this.users.push(user);
}

用 actions 修改 state 更加明確。上面也提到過,有個小功能可以強制的使用 actions 修改 state 。


// root file
import { useStrict } from 'mobx';

useStrict(true);

這樣的話第一個例子中直接修改 store 中的 state 就不再起作用了。前面的例子展示了怎樣擁抱 Mobx 的最佳實踐。此外,一旦你只用 actions ,你就已經使用了 Redux 的約束。

在快速啟動一個項目時,我會推薦使用 Mobx ,一旦應用開始變得越來越大,越來越多的人開發時,遵循最佳實踐就很有意義,如使用明確的 actions 。這是擁抱 Redux 的約束:你永遠不能直接修改 state ,只能使用 actions 。

遷移到 Redux

一旦應用開始變得越來越大,越來越多的人開發時,你應該考慮使用 Redux 。它本身強制使用明確的 actions 修改 state 。action 有 type 和 payload 參數,reducer 可以用來修改 state 。這樣的話,一個團隊裏的開發人員可以很簡單的推斷 state 的修改。


// reducer
(state, action) => newState

Redux 提供狀態管理的整個架構,並有清晰的約束規則。這是 Redux 的成功故事。

另一個 Redux 的優勢是在服務端使用。因為我們使用的是純 JavaScript ,它可以在網絡上傳輸 state 。序列化和反序列化一個 state 對象是直接可用的。當然 Mobx 也是一樣可以的。

Mobx 是無主張的,但你可以通過 useStrict() 像 Redux 一樣使用清晰的約束規則。這就是我為什麽沒說你不能在擴張的應用中使用 Mobx ,但 Redux 是有明確的使用方式的。而 Mobx 甚至在文檔中說:“ Mobx 不會告訴你如何組織代碼,哪裏該存儲 state 或 怎麽處理事件。”所以開發團隊首先要確定 state 的管理架構。

狀態管理的學習曲線並不是很陡峭。我們總結下建議:React 初學者首先學習恰當的使用 setState()this.state 。一段時間之後你將會意識到在 React 應用中僅僅使用 setState() 管理狀態的問題。當你尋找解決方案時,你會在狀態管理庫 Mobx 或 Redux 的選擇上猶豫。應該選哪個呢?由於 Mobx 是無主張的,使用上可以和 setState() 類似,我建議在小項目中嘗試。一旦應用開始變得越來越大,越來越多的人開發時,你應該考慮在 Mobx 上實行更多的限制條件或嘗試使用 Redux 。我使用兩個庫都很享受。即使你最後兩個都沒使用,了解到狀態管理的另一種方式也是有意義的。

嘗試另一個狀態管理方案?

你可能已經使用了其中一個狀態管理方案,但是想考慮另一個?你可以比較現實中的 Mobx 和 Redux 應用。我把所有的文件修改都提交到了一個 Pull Request 。在這個 PR 裏,項目從 Redux 重構成了 Mobx ,反之亦然,你可以自己實現。我不認為有必要和 Redux 或 Mobx 耦合,因為大部分的改變是和其他任何東西解耦的。

你主要需要將 Redux 的 Actions、Action Creator、 Action Types、Reducer、Global Store 替換成 Mobx 的 Stores 。另外將和 React 組件連接的接口 react-redux 換成 mobx-react 。presenter + container pattern 依然可以執行。你僅僅還要重構容器組件。在 Mobx 中可以使用 inject 獲得 store 依賴。然後 store 可以傳遞 substate 和 actions 給組件。Mobx 的 observer 確保組件在 store 中 observable 的屬性變化時更新。


import { observer, inject } from 'mobx-react';

...

const UserProfileContainer = inject(
  'userStore'
)(observer(({
  id,
  userStore,
}) => {
  return (
    <UserProfile
      user={userStore.getUser(id)}
      onUpdateUser={userStore.updateUser}
    />
  );
}));

Redux 的話,你使用 mapStateToPropsmapDispatchToProps 傳遞 substate 和 actions 給組件。


import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

...

function mapStateToProps(state, props) {
  const { id } = props;
  const user = state.users[id];

  return {
    user,
  };
}

function mapDispatchToProps(dispatch) {
  return {
    onUpdateUser: bindActionCreators(actions.updateUser, dispatch),
  };
}

const UserProfileContainer = connect(mapStateToProps, mapDispatchToProps)(UserProfile);

這有一篇怎樣將 Redux 重構為 Mobx指南。但就像我上面說過的,反過來一樣也是可以的。一旦你選擇了一個狀態管理庫,你會知道那並沒有什麽限制。它們基本上是和你的應用解耦的,所以是可以替換的。

最後思考

每當我看 Redux vs Mobx 爭論下的評論時,總會有下面這條:“Redux 有太多的樣板代碼,你應該使用 Mobx,可以減少 xxx 行代碼”。這條評論也許是對的,但沒人考慮得失,Redux 比 Mobx 更多的樣板代碼,是因為特定的設計約束。它允許你推斷應用狀態即使應用規模很大。所以圍繞 state 的儀式都是有原因的。

Redux 庫非常小,大部分時間你都是在處理純 JavaScript 對象和數組。它比 Mobx 更接近 vanilla JavaScript 。Mobx 通過包裝對象和數組為可觀察對象,從而隱藏了大部分的樣板代碼。它是建立在隱藏抽象之上的。感覺像是出現了魔法,但卻很難理解其內在的機制。Redux 則可以簡單通過純 JavaScript 來推斷。它使你的應用更簡單的測試和調試。

另外,我們重新回到單頁應用的最開始來考慮,一系列的單頁應用框架和庫面臨著相同的狀態管理問題,它最終被 flux 模式解決了。Redux 是這個模式的成功者。

Mobx 則又處在相反的方向。我們直接修改 state 而沒有擁抱函數式編程的好處。對一些開發者來說,這讓他們覺得像雙向綁定。一段時間之後,由於沒有引入類似 Redux 的狀態管理庫,他們可能又會陷入同樣的問題。狀態管理分散在各個組件,導致最後一團糟。

使用 Redux,你有一個既定的模式組織代碼,而 Mobx 則無主張。但擁抱 Mobx 最佳實踐會是明智的。 開發者需要知道如何組織狀態管理從而更好的推斷它。不然他們就會想要直接在組件中修改它。

兩個庫都非常棒。Redux 已經非常完善,Mobx 則逐漸成為一個有效的替代。

來源:https://segmentfault.com/a/1190000011148981

【譯】Redux 還是 Mobx,讓我來解決你的困惑!