# 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
- 排错