# Redux 常見問題:性能
## 目錄
- [考慮到性能和架構, Redux “可擴展性” 如何?](#performance-scaling)
- [每個 action 都調用 “所有的 reducer” 會不會很慢?](#performance-all-reducers)
- [在 reducer 中必須對 state 進行深拷貝嗎?拷貝 state 不會很慢嗎?](#performance-clone-state)
- [怎樣減少 store 更新事件的數量?](#performance-update-events)
- [僅有 “一個 state 樹” 會引發內存問題嗎?分發多個 action 會占用內存空間嗎?](#performance-state-memory)
- [緩存遠端數據會造成內存問題嗎?](#performance-cache-memory)
## 性能
<a id="performance-scaling"></a>
### 考慮到性能和架構, Redux “可擴展性” 如何?
沒有一個明確的答案,在大多數情況下都不需要考慮該問題。
Redux 所做的工作可以分為以下幾部分:在 middleware 和 reducer 中處理 action (包括對象復制及不可變更新)、 action 分發之后通知訂閱者、根據 state 變化更新 UI 組件。雖然在一些復雜場景下,這些都 _可能_ 變成一個性能問題,但 Redux 本質上并沒有任何慢或者低效的實現。實際上,React Redux 已經做了大量的優化工作減少不必要的重復渲染,React Redux v5 相比之前的版本有著顯著的改進。
與其他庫相比,Redux 可能沒有那么快。為了更大限度的展示 React 的渲染性能,state 應該以規范化的結構存儲,許多單獨的組件應該直接連接到 store,連接的列表組件應該將項目 ID 傳給子列表(允許列表項通過 ID 查找數據)。這使得要進行渲染的量最小化。使用帶有記憶功能的 selector 函數也對性能有非常大的幫助。
考慮到架構方面,事實證據表明在各種項目及團隊規模下,Redux 都表現出色。Redux 目前正被成百上千的公司以及更多的開發者使用著,NPM 上每月都有幾十萬的安裝量。有一位開發者這樣說:
> 規模方面,我們大約有 500 個 action 類型、400 個 reducer、150 個組件、5 個 middleware、200 個 action、2300 個測試案例。
#### 補充資料
**文檔**
- [Recipes: Structuring Reducers - state 范式化](docs/recipes/reducers/NormalizingStateShape.md)
**文章**
- [How to Scale React Applications ](https://www.smashingmagazine.com/2016/09/how-to-scale-react-applications/)(accompanying talk: [Scaling React Applications](https://vimeo.com/168648012))
- [High-Performance Redux](http://somebody32.github.io/high-performance-redux/)
- [Improving React and Redux Perf with Reselect](http://blog.rangle.io/react-and-redux-performance-with-reselect/)
- [Encapsulating the Redux State Tree](http://randycoulman.com/blog/2016/09/13/encapsulating-the-redux-state-tree/)
- [React/Redux Links: Performance - Redux](https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#redux-performance)
**討論**
- [#310: Who uses Redux?](https://github.com/reactjs/redux/issues/310)
- [#1751: Performance issues with large collections](https://github.com/reactjs/redux/issues/1751)
- [React Redux #269: Connect could be used with a custom subscribe method](https://github.com/reactjs/react-redux/issues/269)
- [React Redux #407: Rewrite connect to offer an advanced API](https://github.com/reactjs/react-redux/issues/407)
- [React Redux #416: Rewrite connect for better performance and extensibility](https://github.com/reactjs/react-redux/issues/416)
- [Redux vs MobX TodoMVC Benchmark: #1](https://github.com/mweststrate/redux-todomvc/pull/1)
- [Reddit: What's the best place to keep the initial state?](https://www.reddit.com/r/reactjs/comments/47m9h5/whats_the_best_place_to_keep_the_initial_state/)
- [Reddit: Help designing Redux state for a single page app](https://www.reddit.com/r/reactjs/comments/48k852/help_designing_redux_state_for_a_single_page/)
- [Reddit: Redux performance issues with a large state object?](https://www.reddit.com/r/reactjs/comments/41wdqn/redux_performance_issues_with_a_large_state_object/)
- [Reddit: React/Redux for Ultra Large Scale apps](https://www.reddit.com/r/javascript/comments/49box8/reactredux_for_ultra_large_scale_apps/)
- [Twitter: Redux scaling](https://twitter.com/NickPresta/status/684058236828266496)
- [Twitter: Redux vs MobX benchmark graph - Redux state shape matters](https://twitter.com/dan_abramov/status/720219615041859584)
- [Stack Overflow: How to optimize small updates to props of nested components?](http://stackoverflow.com/questions/37264415/how-to-optimize-small-updates-to-props-of-nested-component-in-react-redux)
- [Chat log: React/Redux perf - updating a 10K-item Todo list](https://gist.github.com/markerikson/53735e4eb151bc228d6685eab00f5f85)
- [Chat log: React/Redux perf - single connection vs many connections](https://gist.github.com/markerikson/6056565dd65d1232784bf42b65f8b2ad)
<a id="performance-all-reducers"></a>
### 每個 action 都調用 “所有的 reducer” 會不會很慢?
我們應當清楚的認識到 Redux store 只有一個 reducer 方法。 store 將當前的 state 和分發的 action 傳遞給這個 reducer 方法,剩下的就讓 reducer 去處理。
顯然,在單獨的方法里處理所有的 action 僅從方法大小及可讀性方面考慮,就已經很不利于擴展了,所以將實際工作分割成獨立的方法并在頂層的 reducer 中調用就變得很有意義。尤其是目前的建議模式中推薦讓單獨的子 reducer 只負責更新特定的 state 部分。 `combineReducers()` 和 Redux 搭配的方案只是許多實現方式中的一種。強烈建議盡可能保持 store 中 state 的扁平化和范式化,至少你可以隨心所欲的組織你的 reducer 邏輯。
即使你在不經意間已經維護了許多獨立的子 reducer,甚至 state 也是深度嵌套,reducer 的速度也并不構成任何問題。JavaScript 引擎有足夠的能力在每秒運行大量的函數調用,而且大部門的子 reducer 只是使用 `switch` 語句,并且針對大部分 action 返回的都是默認的 state。
如果你仍然關心 reducer 的性能,可以使用類似 [redux-ignore](https://github.com/omnidan/redux-ignore) 和 [reduxr-scoped-reducer](https://github.com/chrisdavies/reduxr-scoped-reducer) 的工具,確保只有某幾個 reducer 響應特定的 action。你還可以使用 [redux-log-slow-reducers](https://github.com/michaelcontento/redux-log-slow-reducers) 進行性能測試。
#### 補充資料
**討論**
- [#912: Proposal: action filter utility](https://github.com/reactjs/redux/issues/912)
- [#1303: Redux Performance with Large Store and frequent updates](https://github.com/reactjs/redux/issues/1303)
- [Stack Overflow: State in Redux app has the name of the reducer](http://stackoverflow.com/questions/35667775/state-in-redux-react-app-has-a-property-with-the-name-of-the-reducer/35674297)
- [Stack Overflow: How does Redux deal with deeply nested models?](http://stackoverflow.com/questions/34494866/how-does-redux-deals-with-deeply-nested-models/34495397)
<a id="performance-clone-state"></a>
### 在 reducer 中必須對 state 進行深拷貝嗎?拷貝 state 不會很慢嗎?
以不可變的方式更新 state 意味著淺拷貝,而非深拷貝。相比于深拷貝,淺拷貝更快,因為只需復制很少的字段和對象,實際的底層實現中也只是移動了若干指針而已。
并且,深拷貝 state 會為每一個層(field)創建新的引用。由于 React-Redux 的 `connect` 函數是比較引用來判斷數據是否變化的,這意味著即使其他數據沒有變化,UI 組件也會被迫進行不必要的重新渲染。
因此,你需要創建一個副本,并且更新受影響的各個嵌套的對象層級即可。盡管上述動作代價不會很大,但這也是為什么需要維護范式化及扁平化 state 的又一充分理由。
> Redux 常見的誤解: 需要深拷貝 state。實際情況是:如果內部的某些數據沒有改變,繼續保持統一引用即可。
#### 補充資料
**文檔**
- [Recipes: Structuring Reducers - Prerequisite Concepts](/docs/faq/docs/recipes/reducers/PrerequisiteConcepts.md)
- [Recipes: Structuring Reducers - Immutable Update Patterns](/docs/recipes/reducers/ImmutableUpdatePatterns.md)
**討論**
- [#454: Handling big states in reducer](https://github.com/reactjs/redux/issues/454)
- [#758: Why can't state be mutated?](https://github.com/reactjs/redux/issues/758)
- [#994: How to cut the boilerplate when updating nested entities?](https://github.com/reactjs/redux/issues/994)
- [Twitter: common misconception - deep cloning](https://twitter.com/dan_abramov/status/688087202312491008)
- [Cloning Objects in JavaScript](http://www.zsoltnagy.eu/cloning-objects-in-javascript/)
<a id="performance-update-events"></a>
### 怎樣減少 store 更新事件的數量?
Redux 在 action 分發成功(例如,action 到達 store 被 reducer 處理)后通知訂閱者。在有些情況下,減少訂閱者被調用的次數會很有用,特別在當 action 創建函數分發了一系列不同的 action 時。
如果你在使用 React,你可以寫在 `ReactDOM.unstable_batchedUpdates()` 以提高同步分發的性能,但這個 API 是實驗性質的,可能會在以后的版本中移除,所以也不要過度依賴它。可以看看一些第三方的實現 [redux-batched-subscribe](https://github.com/tappleby/redux-batched-subscribe)(一個高級的 reducer,可以讓你單獨分發幾個 action)、[redux-batched-subscribe](https://github.com/tappleby/redux-batched-subscribe)(一個 store 增強器,可以平衡多個分發情況下訂閱者的調用次數)和 [redux-batched-actions](https://github.com/tshelburne/redux-batched-actions)(一個 store 增強器,可以利用單個訂閱提醒的方式分發一系列的 action)。
#### 補充資料
**討論**
- [#125: Strategy for avoiding cascading renders](https://github.com/reactjs/redux/issues/125)
- [#542: Idea: batching actions](https://github.com/reactjs/redux/issues/542)
- [#911: Batching actions](https://github.com/reactjs/redux/issues/911)
- [#1813: Use a loop to support dispatching arrays](https://github.com/reactjs/redux/issues/1813)
- [React Redux #263: Huge performance issue when dispatching hundreds of actions](https://github.com/reactjs/react-redux/issues/263)
**庫**
- [Redux Addons Catalog: Store - Change Subscriptions](https://github.com/markerikson/redux-ecosystem-links/blob/master/store.md#store-change-subscriptions)
<a id="performance-state-memory"></a>
### 僅有 “一個 state 樹” 會引發內存問題嗎?分發多個 action 會占用內存空間嗎?
首先,在原始內存使用方面,Redux 和其它的 JavaScript 庫并沒有什么不同。唯一的區別就是所有的對象引用都嵌套在同一棵樹中,而不是像類似于 Backbone 那樣保存在不同的模型實例中。第二,與同樣的 Backbone 應用相比,典型的 Redux 應用可能使用 _更少_ 的內存,因為 Redux 推薦使用普通的 JavaScript 對象和數組,而不是創建模型和集合實例。最后,Redux 僅維護一棵 state 樹。不再被引用的 state 樹通常都會被垃圾回收。
Redux 本身不存儲 action 的歷史。然而,Redux DevTools 會記錄這些 action 以便支持重放,而且也僅在開發環境被允許,生產環境則不會使用。
#### 補充資料
**文檔**
- [Docs: Async Actions](advanced/AsyncActions.md])
**討論**
- [Stack Overflow: Is there any way to "commit" the state in Redux to free memory?](http://stackoverflow.com/questions/35627553/is-there-any-way-to-commit-the-state-in-redux-to-free-memory/35634004)
- [Stack Overflow: Can a Redux store lead to a memory leak?](https://stackoverflow.com/questions/39943762/can-a-redux-store-lead-to-a-memory-leak/40549594#40549594)
- [Stack Overflow: Redux and ALL the application state](https://stackoverflow.com/questions/42489557/redux-and-all-the-application-state/42491766#42491766)
- [Stack Overflow: Memory Usage Concern with Controlled Components](https://stackoverflow.com/questions/44956071/memory-usage-concern-with-controlled-components?noredirect=1&lq=1)
- [Reddit: What's the best place to keep initial state?](https://www.reddit.com/r/reactjs/comments/47m9h5/whats_the_best_place_to_keep_the_initial_state/)
<a id="performance-cache-memory"></a>
### 緩存遠端數據會造成內存問題嗎?
瀏覽器中 JavaScript 應用可以使用的內存是有限的。所以當緩存的體積達到可用內存上限時,就會造成性能問題。然而只有在緩存的數據異常地大,或當前會話(session)異常地長時,這才會是個問題。你能夠意識到這些潛在問題是一件好事,但這不應該妨礙你高效合理地使用緩存。
這里有一些高效緩存遠端數據的方法:
首先,只緩存用戶需要的數據。如果你的應用需要顯示一個經過分頁的記錄列表,你不需要把整個列表緩存下來,而是緩存用戶可見的部分。當用戶需要(或即將需要)更多數據時,將這部分數據加入緩存。
第二,盡可能緩存簡短形式的記錄。有的時候一份記錄包含了與用戶無關的數據,如果這個應用并不依賴于這些數據,就無需緩存這些數據。
第三,只緩存一份記錄的一個拷貝。在一份記錄包含其它記錄的拷貝的情況下,這點尤其重要。對于每份記錄都緩存一份拷貝,然后將其中嵌套的拷貝替換成引用,這個過程叫做范式化。出于[很多原因](/docs/recipes/reducers/NormalizingStateShape.html#designing-a-normalized-state)(比如節省內存),在儲存這種互相關聯的數據時,范式化是最佳實踐。
#### 更多信息
**討論**
- [Stack Overflow: How to choose the Redux state shape for an app with list/detail views and pagination?](https://stackoverflow.com/questions/33940015/how-to-choose-the-redux-state-shape-for-an-app-with-list-detail-views-and-pagina)
- [Twitter: ...concerns over having "too much data in the state tree"...](https://twitter.com/acemarke/status/804071531844423683)
- [Advanced Redux entity normalization](https://medium.com/@dcousineau/advanced-redux-entity-normalization-f5f1fe2aefc5)
- 自述
- 介紹
- 動機
- 核心概念
- 三大原則
- 先前技術
- 學習資源
- 生態系統
- 示例
- 基礎
- Action
- Reducer
- Store
- 數據流
- 搭配 React
- 示例:Todo List
- 高級
- 異步 Action
- 異步數據流
- Middleware
- 搭配 React Router
- 示例:Reddit API
- 下一步
- 技巧
- 配置 Store
- 遷移到 Redux
- 使用對象展開運算符
- 減少樣板代碼
- 服務端渲染
- 編寫測試
- 計算衍生數據
- 實現撤銷重做
- 子應用隔離
- 組織 Reducer
- Reducer 基礎概念
- Reducer 基礎結構
- Reducer 邏輯拆分
- Reducer 重構示例
- combineReducers 用法
- combineReducers 進階
- State 范式化
- 管理范式化數據
- Reducer 邏輯復用
- 不可變更新模式
- 初始化 State
- 結合 Immutable.JS 使用 Redux
- 常見問題
- 綜合
- Reducer
- 組織 State
- 創建 Store
- Action
- 不可變數據
- 代碼結構
- 性能
- 設計哲學
- React Redux
- 其它
- 排錯
- 詞匯表
- API 文檔
- createStore
- Store
- combineReducers
- applyMiddleware
- bindActionCreators
- compose
- react-redux 文檔
- API
- 排錯