Skip to main content

Redux 常见问题解答:反应还原

¥Redux FAQ: React Redux

目录

¥Table of Contents

反应还原

¥React Redux

为什么我应该使用 React-Redux?

¥Why should I use React-Redux?

Redux 本身是一个独立的库,可以与任何 UI 层或框架一起使用,包括 React、Angular、Vue、Ember 和 vanilla JS。虽然 Redux 和 React 通常一起使用,但它们是相互独立的。

¥Redux itself is a standalone library that can be used with any UI layer or framework, including React, Angular, Vue, Ember, and vanilla JS. Although Redux and React are commonly used together, they are independent of each other.

如果你将 Redux 与任何类型的 UI 框架一起使用,你通常会使用 "用户界面绑定" 库将 Redux 与你的 UI 框架结合在一起,而不是直接通过 UI 代码与存储交互。

¥If you are using Redux with any kind of UI framework, you will normally use a "UI binding" library to tie Redux together with your UI framework, rather than directly interacting with the store from your UI code.

React-Redux 是 React 的官方 Redux UI 绑定库。如果你同时使用 Redux 和 React,你还应该使用 React-Redux 来绑定这两个库。

¥React-Redux is the official Redux UI binding library for React. If you are using Redux and React together, you should also use React-Redux to bind these two libraries.

虽然可以手动编写 Redux 存储订阅逻辑,但这样做会变得非常重复。此外,优化 UI 性能需要复杂的逻辑。

¥While it is possible to write Redux store subscription logic by hand, doing so would become very repetitive. In addition, optimizing UI performance would require complicated logic.

订阅存储、检查更新数据和触发重新渲染的过程可以变得更加通用和可重用。像 React-Redux 这样的 UI 绑定库可以处理存储交互逻辑,因此你不必自己编写该代码。

¥The process of subscribing to the store, checking for updated data, and triggering a re-render can be made more generic and reusable. A UI binding library like React-Redux handles the store interaction logic, so you don't have to write that code yourself.

总体而言,React-Redux 鼓励良好的 React 架构,并为你实现复杂的性能优化。它还与 Redux 和 React 的最新 API 更改保持同步。

¥Overall, React-Redux encourages good React architecture, and implements complex performance optimizations for you. It is also kept up-to-date with the latest API changes from Redux and React.

更多信息

¥Further Information

文档

¥Documentation

为什么我的组件没有重新渲染,或者我的 mapStateToProps 没有运行?

¥Why isn't my component re-rendering, or my mapStateToProps running?

意外地直接改变或修改状态是迄今为止组件在分派操作后不重新渲染的最常见原因。Redux 期望你的 reducer 能够“不可变地”更新其状态,这实际上意味着始终复制你的数据,并将更改应用到副本。如果你从 reducer 返回相同的对象,Redux 会假设没有任何更改,即使你对其内容进行了更改。类似地,React Redux 尝试通过对 shouldComponentUpdate 中传入的 props 进行浅层相等引用检查来提高性能,如果所有引用都相同,则 shouldComponentUpdate 返回 false 以跳过实际更新原始组件。

¥Accidentally mutating or modifying your state directly is by far the most common reason why components do not re-render after an action has been dispatched. Redux expects that your reducers will update their state “immutably”, which effectively means always making copies of your data, and applying your changes to the copies. If you return the same object from a reducer, Redux assumes that nothing has been changed, even if you made changes to its contents. Similarly, React Redux tries to improve performance by doing shallow equality reference checks on incoming props in shouldComponentUpdate, and if all references are the same, shouldComponentUpdate returns false to skip actually updating your original component.

重要的是要记住,每当你更新嵌套值时,你还必须返回状态树中该值之上的任何内容的新副本。如果你有 state.a.b.c.d,并且想要更新 d,则还需要返回 cbastate 的新副本。此 状态树突变图 演示了树深处的更改如何需要一路向上的更改。

¥It's important to remember that whenever you update a nested value, you must also return new copies of anything above it in your state tree. If you have state.a.b.c.d, and you want to make an update to d, you would also need to return new copies of c, b, a, and state. This state tree mutation diagram demonstrates how a change deep in a tree requires changes all the way up.

请注意,“不可变地更新数据”并不意味着你必须使用 伊梅尔,尽管这当然是一个选项。你可以使用几种不同的方法对普通 JS 对象和数组进行不可变更新:

¥Note that “updating data immutably” does not mean that you must use Immer, although that is certainly an option. You can do immutable updates to plain JS objects and arrays using several different approaches:

  • 使用 Object.assign()_.extend() 等函数以及 slice()concat() 等数组函数复制对象

    ¥Copying objects using functions like Object.assign() or _.extend(), and array functions such as slice() and concat()

  • ES2015 中的数组扩展运算符,以及 ES2018 中的类似对象扩展运算符

    ¥The array spread operator in ES2015, and the similar object spread operator from ES2018

  • 将不可变的更新逻辑封装成更简单的函数的实用程序库

    ¥Utility libraries that wrap immutable update logic into simpler functions

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions

为什么我的组件重新渲染过于频繁?

¥Why is my component re-rendering too often?

React Redux 实现了多项优化,以确保你的实际组件仅在实际需要时重新渲染。其中之一是对传递给 connectmapStateToPropsmapDispatchToProps 参数生成的组合 props 对象进行浅层相等检查。不幸的是,在每次调用 mapStateToProps 时都会创建新数组或对象实例的情况下,浅层相等没有帮助。一个典型的示例可能是映射 ID 数组并返回匹配的对象引用,例如:

¥React Redux implements several optimizations to ensure your actual component only re-renders when actually necessary. One of those is a shallow equality check on the combined props object generated by the mapStateToProps and mapDispatchToProps arguments passed to connect. Unfortunately, shallow equality does not help in cases where new array or object instances are created each time mapStateToProps is called. A typical example might be mapping over an array of IDs and returning the matching object references, such as:

const mapStateToProps = state => {
return {
objects: state.objectIds.map(id => state.objects[id])
}
}

尽管数组每次可能包含完全相同的对象引用,但数组本身是不同的引用,因此浅层相等检查失败,React Redux 将重新渲染封装的组件。

¥Even though the array might contain the exact same object references each time, the array itself is a different reference, so the shallow equality check fails and React Redux would re-render the wrapped component.

额外的重新渲染可以通过以下方式解决:使用化简器将对象数组保存到状态中,使用 重新选择 缓存映射数组,或者在组件中手动实现 shouldComponentUpdate 并使用 _.isEqual 等函数进行更深入的 props 比较。小心不要让你的定制 shouldComponentUpdate() 比渲染本身更贵!始终使用分析器来检查你对性能的假设。

¥The extra re-renders could be resolved by saving the array of objects into the state using a reducer, caching the mapped array using Reselect, or implementing shouldComponentUpdate in the component by hand and doing a more in-depth props comparison using a function such as _.isEqual. Be careful to not make your custom shouldComponentUpdate() more expensive than the rendering itself! Always use a profiler to check your assumptions about performance.

对于非连接组件,你可能需要检查传入的 props 是什么。一个常见的问题是让父组件在其渲染函数内重新绑定回调,例如 <Child onClick={this.handleClick.bind(this)} />。每次父级重新渲染时都会创建一个新的函数引用。通常最好的做法是仅在父组件的构造函数中绑定回调一次。

¥For non-connected components, you may want to check what props are being passed in. A common issue is having a parent component re-bind a callback inside its render function, like <Child onClick={this.handleClick.bind(this)} />. That creates a new function reference every time the parent re-renders. It's generally good practice to only bind callbacks once in the parent component's constructor.

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions

¥Libraries

我怎样才能加快我的 mapStateToProps 速度?

¥How can I speed up my mapStateToProps?

虽然 React Redux 确实可以最大限度地减少 mapStateToProps 函数的调用次数,但确保 mapStateToProps 快速运行并最大限度地减少其工作量仍然是一个好主意。常见的推荐方法是使用 重新选择 创建记忆的“选择器”函数。这些选择器可以组合和组合在一起,并且管道中稍后的选择器仅在其输入发生更改时才会运行。这意味着你可以创建执行过滤或排序等操作的选择器,并确保仅在需要时才进行真正的工作。

¥While React Redux does work to minimize the number of times that your mapStateToProps function is called, it's still a good idea to ensure that your mapStateToProps runs quickly and also minimizes the amount of work it does. The common recommended approach is to create memoized “selector” functions using Reselect. These selectors can be combined and composed together, and selectors later in a pipeline will only run if their inputs have changed. This means you can create selectors that do things like filtering or sorting, and ensure that the real work only happens if needed.

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions

为什么我的连接组件中没有可用的 this.props.dispatch

¥Why don't I have this.props.dispatch available in my connected component?

connect() 函数有两个主要参数,都是可选的。第一个 mapStateToProps 是你提供的一个函数,用于在数据发生变化时从存储中提取数据,并将这些值作为 props 传递给你的组件。第二个 mapDispatchToProps 是你提供的一个函数,用于利用存储的 dispatch 函数,通常是通过创建动作创建器的预绑定版本,一旦调用它们就会自动调度它们的动作。

¥The connect() function takes two primary arguments, both optional. The first, mapStateToProps, is a function you provide to pull data from the store when it changes, and pass those values as props to your component. The second, mapDispatchToProps, is a function you provide to make use of the store's dispatch function, usually by creating pre-bound versions of action creators that will automatically dispatch their actions as soon as they are called.

如果你在调用 connect() 时没有提供自己的 mapDispatchToProps 函数,React Redux 将提供一个默认版本,它只是返回 dispatch 函数作为 prop。这意味着如果你确实提供了自己的函数,则不会自动提供 dispatch。如果你仍然希望它作为 prop 可用,你需要在 mapDispatchToProps 实现中自己显式返回它。

¥If you do not provide your own mapDispatchToProps function when calling connect(), React Redux will provide a default version, which simply returns the dispatch function as a prop. That means that if you do provide your own function, dispatch is not automatically provided. If you still want it available as a prop, you need to explicitly return it yourself in your mapDispatchToProps implementation.

更多信息

¥Further information

文档

¥Documentation

讨论

¥Discussions

我应该只连接顶部组件,还是可以连接树中的多个组件?

¥Should I only connect my top component, or can I connect multiple components in my tree?

早期的 Redux 文档建议你应该只在组件树顶部附近有几个连接的组件。然而,时间和经验表明,这样的组件架构通常需要少数组件过多地了解其所有后代的数据需求,并迫使它们传递大量令人困惑的 props。

¥Early Redux documentation advised that you should only have a few connected components near the top of your component tree. However, time and experience has shown that such a component architecture generally requires a few components to know too much about the data requirements of all their descendants, and forces them to pass down a confusing number of props.

当前建议的最佳实践是将组件分类为“演示”或“容器”组件,并在任何有意义的地方提取连接的容器组件:

¥The current suggested best practice is to categorize your components as “presentational” or “container” components, and extract a connected container component wherever it makes sense:

在 Redux 示例中强调“顶部有一个容器组件”是一个错误。不要将此视为格言。尝试将演示组件分开。在方便的时候通过连接它们来创建容器组件。每当你觉得要在父组件中复制代码以为同类子组件提供数据时,就需要提取容器了。一般来说,一旦你觉得父级对其子级的“个人”数据或操作了解太多,就该提取容器了。

¥Emphasizing “one container component at the top” in Redux examples was a mistake. Don't take this as a maxim. Try to keep your presentation components separate. Create container components by connecting them when it's convenient. Whenever you feel like you're duplicating code in parent components to provide data for same kinds of children, time to extract a container. Generally as soon as you feel a parent knows too much about “personal” data or actions of its children, time to extract a container.

事实上,基准测试表明,连接的组件越多通常会比连接的组件越少带来更好的性能。

¥In fact, benchmarks have shown that more connected components generally leads to better performance than fewer connected components.

一般来说,尝试在可理解的数据流和组件的责任范围之间找到平衡。

¥In general, try to find a balance between understandable data flow and areas of responsibility with your components.

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions

Redux 与 React Context API 相比如何?

¥How does Redux compare to the React Context API?

相似之处

¥Similarities

Redux 和 React 的 Context API 都处理 "属性钻井"。也就是说,它们都允许你传递数据,而无需通过多层组件传递 props。在内部,Redux 使用 React context API,允许它沿着组件树传递存储。

¥Both Redux and React's Context API deal with "prop drilling". That said, they both allow you to pass data without having to pass the props through multiple layers of components. Internally, Redux uses the React context API that allows it to pass the store along your component tree.

差异

¥Differences

借助 Redux,你可以获得 Redux 开发工具扩展 的强大功能。它会自动记录你的应用执行的每个操作,并且允许时间旅行 - 你可以单击任何过去的操作并跳转到该时间点。Redux 还支持中间件的概念,你可以在每个操作调度上绑定自定义函数调用。此类示例包括自动事件日志器、某些操作的拦截等。

¥With Redux, you get the power of Redux Dev Tools Extension. It automatically logs every action your app performs, and it allows time traveling – you can click on any past action and jump to that point in time. Redux also supports the concept of middleware, where you may bind customized function calls on every action dispatch. Such examples include an automatic event logger, interception of certain actions, etc.

使用 React 的 Context API,你可以处理一对仅相互通信的组件。这可以让你很好地隔离不相关的数据。你还可以灵活地在组件中使用数据,即,你可以提供父组件的状态,并且可以将上下文数据作为 props 传递给封装的组件。

¥With React's Context API, you deal with a pair of components speaking only to each other. This gives you nice isolation between irrelevant data. You also have the flexibility of how you may use the data with your components, i.e., you can provide the state of a parent component, and you may pass context data as props to wrapped components.

Redux 和 React 的 Context 处理数据的方式有一个关键的区别。Redux 将整个应用的数据维护在一个巨大的、有状态的对象中。它通过运行你提供的 reducer 函数来推断数据的更改,并返回与所调度的每个操作相对应的下一个状态。然后,React Redux 会优化组件渲染,并确保每个组件仅在需要的数据发生变化时才重新渲染。另一方面,上下文不保存任何状态。它只是数据的管道。要表达数据的更改,你需要依赖父组件的状态。

¥There is a key difference in how Redux and React's Context treat data. Redux maintains the data of your whole app in a giant, stateful object. It deduces the changes of your data by running the reducer function you provide, and returns the next state that corresponds to every action dispatched. React Redux then optimizes component rendering and makes sure that each component re-renders only when the data it needs change. Context, on the other hand, does not hold any state. It is only a conduit for the data. To express changes in data you need to rely on the state of a parent component.

更多信息

¥Further information