Skip to main content

Redux 常见问题解答:表现

¥Redux FAQ: Performance

目录

¥Table of Contents

表现

¥Performance

Redux 在性能和架构方面的“扩展”程度如何?

¥How well does Redux “scale” in terms of performance and architecture?

虽然对此没有一个明确的答案,但大多数情况下,这两种情况都不应成为问题。

¥While there's no single definitive answer to this, most of the time this should not be a concern in either case.

Redux 所做的工作一般分为几个字段:处理中间件和化简器中的操作(包括用于不可变更新的对象复制),在分派操作后通知订阅者,以及根据状态更改更新 UI 组件。虽然在足够复杂的情况下,每个问题都可能成为性能问题,但 Redux 的实现方式并没有本质上缓慢或低效的地方。事实上,React Redux 特别进行了大量优化,以减少不必要的重新渲染,并且 React-Redux v5 比早期版本显示出显着的改进。

¥The work done by Redux generally falls into a few areas: processing actions in middleware and reducers (including object duplication for immutable updates), notifying subscribers after actions are dispatched, and updating UI components based on the state changes. While it's certainly possible for each of these to become a performance concern in sufficiently complex situations, there's nothing inherently slow or inefficient about how Redux is implemented. In fact, React Redux in particular is heavily optimized to cut down on unnecessary re-renders, and React-Redux v5 shows noticeable improvements over earlier versions.

与其他库相比,Redux 开箱即用的效率可能不高。为了在 React 应用中获得最大的渲染性能,状态应该以规范化的形状存储,许多单独的组件应该连接到存储,而不是只有几个,并且连接的列表组件应该将项目 ID 传递到其连接的子列表项(允许列表项通过 ID 查找自己的数据)。这可以最大限度地减少要完成的渲染总量。使用记忆选择器函数也是一个重要的性能考虑因素。

¥Redux may not be as efficient out of the box when compared to other libraries. For maximum rendering performance in a React application, state should be stored in a normalized shape, many individual components should be connected to the store instead of just a few, and connected list components should pass item IDs to their connected child list items (allowing the list items to look up their own data by ID). This minimizes the overall amount of rendering to be done. Use of memoized selector functions is also an important performance consideration.

至于架构,传闻证据表明 Redux 适合不同的项目和团队规模。Redux 目前被数百家公司和数千名开发者使用,NPM 每月有数十万次安装。一位开发商报告称:

¥As for architecture, anecdotal evidence is that Redux works well for varying project and team sizes. Redux is currently used by hundreds of companies and thousands of developers, with several hundred thousand monthly installations from NPM. One developer reported:

对于规模,我们有〜500 个动作类型,〜400 个 reducer 案例,〜150 个组件,5 个中间件,〜200 个动作,〜2300 个测试

¥for scale, we have ~500 action types, ~400 reducer cases, ~150 components, 5 middlewares, ~200 actions, ~2300 tests

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions

为每个动作调用“我所有的 reducer”会不会很慢?

¥Won't calling “all my reducers” for each action be slow?

值得注意的是,Redux 存储实际上只有一个 reducer 函数。存储将当前状态和分派操作传递给该 reducer 函数,并让 reducer 适当地处理事情。

¥It's important to note that a Redux store really only has a single reducer function. The store passes the current state and dispatched action to that one reducer function, and lets the reducer handle things appropriately.

显然,仅仅在函数大小和可读性方面,尝试在单个函数中处理每个可能的操作并不能很好地扩展,因此将实际工作拆分为可由顶层 reducer 调用的单独函数是有意义的。特别是,常见的建议模式是拥有一个单独的子缩减器函数,负责管理特定键上特定状态片的更新。Redux 附带的 combineReducers() 是实现这一目标的众多可能方法之一。强烈建议你的存储状态尽可能保持平坦和标准化。但最终,你负责以你想要的任何方式组织你的 reducer 逻辑。

¥Obviously, trying to handle every possible action in a single function does not scale well, simply in terms of function size and readability, so it makes sense to split the actual work into separate functions that can be called by the top-level reducer. In particular, the common suggested pattern is to have a separate sub-reducer function that is responsible for managing updates to a particular slice of state at a specific key. The combineReducers() that comes with Redux is one of the many possible ways to achieve this. It's also highly suggested to keep your store state as flat and as normalized as possible. Ultimately, though, you are in charge of organizing your reducer logic any way you want.

然而,即使你碰巧将许多不同的 reducer 函数组合在一起,并且即使具有深度嵌套状态,reducer 速度也不太可能成为问题。JavaScript 引擎每秒能够运行大量函数调用,并且大多数 reducer 可能只是使用 switch 语句并默认返回现有状态以响应大多数操作。

¥However, even if you happen to have many different reducer functions composed together, and even with deeply nested state, reducer speed is unlikely to be a problem. JavaScript engines are capable of running a very large number of function calls per second, and most of your reducers are probably just using a switch statement and returning the existing state by default in response to most actions.

如果你确实关心 reducer 性能,则可以使用 redux-ignorereduxr-scoped-reducer 等实用程序来确保只有某些 reducer 监听特定操作。你还可以使用 redux-log-slow-reducers 进行一些性能基准测试。

¥If you actually are concerned about reducer performance, you can use a utility such as redux-ignore or reduxr-scoped-reducer to ensure that only certain reducers listen to specific actions. You can also use redux-log-slow-reducers to do some performance benchmarking.

更多信息

¥Further information

讨论

¥Discussions

我是否必须在 reducer 中深度克隆我的状态?复制我的状态会不会很慢?

¥Do I have to deep-clone my state in a reducer? Isn't copying my state going to be slow?

不可变地更新状态通常意味着进行浅复制,而不是深复制。浅拷贝比深拷贝快得多,因为需要复制的对象和字段更少,而且它实际上可以归结为移动一些指针。

¥Immutably updating state generally means making shallow copies, not deep copies. Shallow copies are much faster than deep copies, because fewer objects and fields have to be copied, and it effectively comes down to moving some pointers around.

此外,深度克隆状态为各个字段创造了新的参考。由于 React-Redux connect 函数依赖于引用比较来确定数据是否已更改,这意味着即使其他数据没有发生有意义的更改,UI 组件也将被迫进行不必要的重新渲染。

¥In addition, deep cloning state creates new references for every field. Since the React-Redux connect function relies on reference comparisons to determine if data has changed, this means that UI components will be forced to re-render unnecessarily even though the other data hasn't meaningfully changed.

但是,你确实需要为受影响的每个嵌套级别创建复制和更新的对象。尽管这应该不会特别昂贵,但这是你应该在可能的情况下保持状态正常化和浅层的另一个很好的理由。

¥However, you do need to create a copied and updated object for each level of nesting that is affected. Although that shouldn't be particularly expensive, it's another good reason why you should keep your state normalized and shallow if possible.

常见的 Redux 误解:你需要深度克隆状态。现实:如果内部的某些内容没有改变,请保持其引用相同!

¥Common Redux misconception: you need to deeply clone the state. Reality: if something inside doesn't change, keep its reference the same!

更多信息

¥Further information

文档

¥Documentation

讨论

¥Discussions

如何减少存储更新事件的数量?

¥How can I reduce the number of store update events?

Redux 在每个成功分派操作(即操作到达存储并由 reducer 处理)后通知订阅者。在某些情况下,减少订阅者被调用的次数可能很有用,特别是当动作创建者连续调度多个不同的动作时。

¥Redux notifies subscribers after each successfully dispatched action (i.e. an action reached the store and was handled by reducers). In some cases, it may be useful to cut down on the number of times subscribers are called, particularly if an action creator dispatches multiple distinct actions in a row.

有几个插件可以以各种方式添加批处理功能,例如:redux-batched-actions(一种高阶 reducer,可让你分派多个操作,就好像它是一个操作一样,并将它们“解压”到 reducer 中)、redux-batched-subscribe(一种存储增强器,可让你消除多次调度的订阅者调用)或 redux-batch(一种存储增强器) 处理通过单个订阅者通知调度一系列操作)。

¥There are several addons that add batching capabilities in various ways, like: redux-batched-actions (a higher-order reducer that lets you dispatch several actions as if it was one and “unpack” them in the reducer), redux-batched-subscribe (a store enhancer that lets you debounce subscriber calls for multiple dispatches), or redux-batch (a store enhancer that handles dispatching an array of actions with a single subscriber notification).

特别是对于 React-Redux,从 React-Redux v7 开始,可以使用新的 batch 公共 API 来帮助在 React 事件处理程序之外分派操作时最大程度地减少 React 重新渲染的数量。它封装了 React 的 unstable_batchedUpdate() API,允许事件循环中的任何 React 更新一起批处理到单个渲染通道中。React 已经在内部将其用于自己的事件处理程序回调。这个 API 实际上是 ReactDOM 和 React Native 等渲染器包的一部分,而不是 React 核心本身。

¥For React-Redux specifically, starting in React-Redux v7 a new batch public API is available to help minimize the number of React re-renders when dispatching actions outside of React event handlers. It wraps React's unstable_batchedUpdate() API, allows any React updates in an event loop tick to be batched together into a single render pass. React already uses this internally for its own event handler callbacks. This API is actually part of the renderer packages like ReactDOM and React Native, not the React core itself.

由于 React-Redux 需要在 ReactDOM 和 React Native 环境中工作,因此我们在构建时从正确的渲染器导入此 API 供我们自己使用。我们现在也重新公开了这个函数,并将其重命名为 batch()。你可以使用它来确保在 React 之外分派的多个操作仅导致单个渲染更新,如下所示:

¥Since React-Redux needs to work in both ReactDOM and React Native environments, we've taken care of importing this API from the correct renderer at build time for our own use. We also now re-export this function publicly ourselves, renamed to batch(). You can use it to ensure that multiple actions dispatched outside of React only result in a single render update, like this:

import { batch } from 'react-redux'

function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}

更多信息

¥Further information

讨论

¥Discussions

¥Libraries

“一个状态树”会导致内存问题吗?调度很多动作会占用内存吗?

¥Will having “one state tree” cause memory problems? Will dispatching many actions take up memory?

首先,就原始内存使用而言,Redux 与任何其他 JavaScript 库没有什么不同。唯一的区别是所有不同的对象引用都嵌套到一棵树中,而不是保存在各种独立的模型实例中,例如 Backbone 中。其次,典型的 Redux 应用的内存使用量可能会比同等的 Backbone 应用少一些,因为 Redux 鼓励使用纯 JavaScript 对象和数组,而不是创建模型和集合的实例。最后,Redux 一次仅保留一个状态树引用。像往常一样,该树中不再引用的对象将被垃圾收集。

¥First, in terms of raw memory usage, Redux is no different than any other JavaScript library. The only difference is that all the various object references are nested together into one tree, instead of maybe saved in various independent model instances such as in Backbone. Second, a typical Redux app would probably have somewhat less memory usage than an equivalent Backbone app because Redux encourages use of plain JavaScript objects and arrays rather than creating instances of Models and Collections. Finally, Redux only holds onto a single state tree reference at a time. Objects that are no longer referenced in that tree will be garbage collected, as usual.

Redux 本身不存储操作历史记录。然而,Redux DevTools 确实会存储操作,以便可以重播它们,但这些操作通常仅在开发期间启用,而不会在生产中使用。

¥Redux does not store a history of actions itself. However, the Redux DevTools do store actions so they can be replayed, but those are generally only enabled during development, and not used in production.

更多信息

¥Further information

文档

¥Documentation

讨论

¥Discussions

缓存远程数据会导致内存问题吗?

¥Will caching remote data cause memory problems?

在浏览器中运行的 JavaScript 应用可用的内存量是有限的。当缓存的大小接近可用内存量时,缓存数据将导致性能问题。当缓存的数据特别大或者会话运行时间特别长时,这往往会成为一个问题。虽然意识到这些问题的可能性是件好事,但这种认识不应阻止你有效地缓存合理数量的数据。

¥The amount of memory available to JavaScript applications running in a browser is finite. Caching data will cause performance problems when the size of the cache approaches the amount of available memory. This tends to be a problem when the cached data is exceptionally large or the session is exceptionally long-running. And while it is good to be aware of the potential for these problems, this awareness should not discourage you from efficiently caching reasonable amounts of data.

以下是有效缓存远程数据的几种方法:

¥Here are a few approaches to caching remote data efficiently:

首先,仅缓存用户需要的数据。如果你的应用显示分页记录列表,则不一定需要缓存整个集合。相反,缓存用户可见的内容,并在用户(或即将)立即需要更多数据时添加到该缓存。

¥First, only cache as much data as the user needs. If your application displays a paginated list of records, you don't necessarily need to cache the entire collection. Instead, cache what is visible to the user and add to that cache when the user has (or will soon have) an immediate need for more data.

其次,尽可能缓存记录的缩写形式。有时记录包含与用户无关的数据。如果应用不依赖此数据,则可以将其从缓存中省略。

¥Second, cache an abbreviated form of a record when possible. Sometimes a record includes data that is not relevant to the user. If the application does not depend on this data, it can be omitted from the cache.

第三,仅缓存记录的单个副本。当记录包含其他记录的副本时,这一点尤其重要。为每条记录缓存一个唯一的副本,并用引用替换每个嵌套副本。这称为标准化。标准化是存储 几个原因 关系数据的首选方法,包括有效的内存消耗。

¥Third, only cache a single copy of a record. This is especially important when records contain copies of other records. Cache a unique copy for each record and replace each nested copy with a reference. This is called normalization. Normalization is the preferred approach to storing relational data for several reasons, including efficient memory consumption.

更多信息

¥Further information

讨论

¥Discussions