Skip to main content

Redux 常见问题解答:行动

¥Redux FAQ: Actions

目录

¥Table of Contents

行动

¥Actions

为什么 type 应该是一个字符串?为什么我的操作类型应该是常量?

¥Why should type be a string? Why should my action types be constants?

与状态一样,可序列化操作启用了 Redux 的多个定义功能,例如时间旅行调试以及记录和重放操作。使用诸如 Symbol 之类的东西作为 type 值或使用 instanceof 检查操作本身会破坏这一点。字符串是可序列化的并且易于自我描述,因此是更好的选择。请注意,如果操作旨在供中间件使用,则可以在操作中使用符号、promise 或其他不可序列化的值。操作只需要在实际到达存储并传递给 reducer 时可序列化。

¥As with state, serializable actions enable several of Redux's defining features, such as time travel debugging, and recording and replaying actions. Using something like a Symbol for the type value or using instanceof checks for actions themselves would break that. Strings are serializable and easily self-descriptive, and so are a better choice. Note that it is okay to use Symbols, Promises, or other non-serializable values in an action if the action is intended for use by middleware. Actions only need to be serializable by the time they actually reach the store and are passed to the reducers.

出于性能原因,我们无法可靠地强制执行可序列化的操作,因此 Redux 仅检查每个操作是否都是普通对象,并且 type 是字符串。剩下的取决于你,但你可能会发现保持所有内容可序列化有助于调试和重现问题。

¥We can't reliably enforce serializable actions for performance reasons, so Redux only checks that every action is a plain object, and that the type is a string. The rest is up to you, but you might find that keeping everything serializable helps debug and reproduce issues.

封装和集中常用的代码片段是编程中的一个关键概念。虽然当然可以在任何地方手动创建操作对象,并手动编写每个 type 值,但定义可重用常量使维护代码变得更容易。如果将常量放在单独的文件中,则可以 检查你的 import 语句是否有拼写错误,这样就不会意外地使用错误的字符串。

¥Encapsulating and centralizing commonly used pieces of code is a key concept in programming. While it is certainly possible to manually create action objects everywhere, and write each type value by hand, defining reusable constants makes maintaining code easier. If you put constants in a separate file, you can check your import statements against typos so you can't accidentally use the wrong string.

更多信息

¥Further information

文档

¥Documentation

讨论

¥Discussion

reducer 和操作之间是否始终存在一对一的映射?

¥Is there always a one-to-one mapping between reducers and actions?

不。我们建议你编写独立的小型 reducer 函数,每个函数负责更新特定的状态片。我们将这种模式称为“reducer 组合”。给定的操作可以由所有人、部分人或任何人都不处理。这使得组件与实际数据更改分离,因为一个操作可能会影响状态树的不同部分,并且组件不需要意识到这一点。有些用户确实选择将它们更紧密地绑定在一起,例如“ducks”文件结构,但默认情况下绝对没有一对一的映射,并且你应该随时打破这种范式 处理许多 reducer 中的动作。

¥No. We suggest you write independent small reducer functions that are each responsible for updates to a specific slice of state. We call this pattern “reducer composition”. A given action could be handled by all, some, or none of them. This keeps components decoupled from the actual data changes, as one action may affect different parts of the state tree, and there is no need for the component to be aware of this. Some users do choose to bind them more tightly together, such as the “ducks” file structure, but there is definitely no one-to-one mapping by default, and you should break out of such a paradigm any time you feel you want to handle an action in many reducers.

更多信息

¥Further information

文档

¥Documentation

讨论

¥Discussions

如何表示 AJAX 调用等“副作用”?为什么我们需要“动作创建者”、“thunk”和“中间件”之类的东西来执行异步行为?

¥How can I represent “side effects” such as AJAX calls? Why do we need things like “action creators”, “thunks”, and “middleware” to do async behavior?

这是一个漫长而复杂的主题,对于如何组织代码以及应该使用什么方法有各种各样的意见。

¥This is a long and complex topic, with a wide variety of opinions on how code should be organized and what approaches should be used.

任何有意义的 Web 应用都需要执行复杂的逻辑,通常包括异步工作,例如发出 AJAX 请求。该代码不再纯粹是其输入的函数,与外界的交互被称为 “副作用”

¥Any meaningful web app needs to execute complex logic, usually including asynchronous work such as making AJAX requests. That code is no longer purely a function of its inputs, and the interactions with the outside world are known as “side effects”

Redux 的灵感来自于函数式编程,而且开箱即用,没有地方可以执行副作用。特别是,reducer 函数必须始终是 (state, action) => newState 的纯函数。然而,Redux 的中间件可以拦截分派的操作并在其周围添加额外的复杂行为,包括副作用。

¥Redux is inspired by functional programming, and out of the box, has no place for side effects to be executed. In particular, reducer functions must always be pure functions of (state, action) => newState. However, Redux's middleware makes it possible to intercept dispatched actions and add additional complex behavior around them, including side effects.

一般来说,Redux 建议具有副作用的代码应该成为操作创建过程的一部分。虽然该逻辑可以在 UI 组件内部执行,但将该逻辑提取到可重用函数中通常是有意义的,以便可以从多个位置调用相同的逻辑 - 换句话说,动作创建器函数。

¥In general, Redux suggests that code with side effects should be part of the action creation process. While that logic can be performed inside of a UI component, it generally makes sense to extract that logic into a reusable function so that the same logic can be called from multiple places—in other words, an action creator function.

最简单和最常见的方法是添加 Redux Thunk 中间件,它允许你编写具有更复杂和异步逻辑的操作创建器。另一种广泛使用的方法是 Redux 传奇,它允许你使用生成器编写更多看起来同步的代码,并且可以像 Redux 应用中的“后台线程”或“守护进程”一样运行。另一种方法是 还原循环,它通过允许你的 reducer 声明响应状态更改的副作用并让它们单独执行来反转该过程。除此之外,还有许多其他社区开发的库和想法,每个库和想法对于如何管理副作用都有自己的看法。

¥The simplest and most common way to do this is to add the Redux Thunk middleware that lets you write action creators with more complex and asynchronous logic. Another widely-used method is Redux Saga which lets you write more synchronous-looking code using generators, and can act like “background threads” or “daemons” in a Redux app. Yet another approach is Redux Loop, which inverts the process by allowing your reducers to declare side effects in response to state changes and have them executed separately. Beyond that, there are many other community-developed libraries and ideas, each with their own take on how side effects should be managed.

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions

我应该使用什么异步中间件?你如何在 thunk、saga、observable 或其他东西之间做出选择?

¥What async middleware should I use? How do you decide between thunks, sagas, observables, or something else?

许多可用的异步/副作用中间件,但最常用的是 redux-thunkredux-sagaredux-observable。这些是不同的工具,具有不同的优点、缺点和用例。

¥There are many async/side effect middlewares available, but the most commonly used ones are redux-thunk, redux-saga, and redux-observable. These are different tools, with different strengths, weaknesses, and use cases.

作为一般经验法则:

¥As a general rule of thumb:

  • Thunk 最适合复杂的同步逻辑(尤其是需要访问整个 Redux 存储状态的代码)和简单的异步逻辑(如基本的 AJAX 调用)。通过使用 async/await,对于一些更复杂的基于 Promise 的逻辑使用 thunk 也是合理的。

    ¥Thunks are best for complex synchronous logic (especially code that needs access to the entire Redux store state), and simple async logic (like basic AJAX calls). With the use of async/await, it can be reasonable to use thunks for some more complex promise-based logic as well.

  • Sagas 最适合复杂的异步逻辑和解耦的 "后台线程" 类型行为,特别是如果你需要监听分派的操作(这是无法使用 thunk 完成的事情)。他们需要熟悉生成器函数和 redux-saga 的 "effects" 运算符。

    ¥Sagas are best for complex async logic and decoupled "background thread"-type behavior, especially if you need to listen to dispatched actions (which is something that can't be done with thunks). They require familiarity with generator functions and redux-saga's "effects" operators.

  • Observables 解决了与 sagas 相同的问题,但依赖 RxJS 来实现异步行为。他们需要熟悉 RxJS API。

    ¥Observables solve the same problems as sagas, but rely on RxJS to implement async behavior. They require familiarity with the RxJS API.

我们建议大多数 Redux 用户应该从 thunk 开始,然后如果他们的应用确实需要处理更复杂的异步逻辑,则稍后添加额外的副作用库,例如 sagas 或 observables。

¥We recommend that most Redux users should start with thunks, and then add an additional side effect library like sagas or observables later if their app really requires handling for more complex async logic.

由于 sagas 和 observables 具有相同的用例,应用通常会使用其中之一,但不会同时使用两者。但是,请注意,同时使用 thunk 和 sagas 或 observable 绝对没问题,因为它们解决不同的问题。

¥Since sagas and observables have the same use case, an application would normally use one or the other, but not both. However, note that it's absolutely fine to use both thunks and either sagas or observables together, because they solve different problems.

文章

¥Articles

讨论

¥Discussions

我应该从一个操作创建者连续分派多个操作吗?

¥Should I dispatch multiple actions in a row from one action creator?

对于如何构建你的行动并没有具体的规则。使用像 Redux Thunk 这样的异步中间件当然可以实现诸如连续分派多个不同但相关的操作、分派操作来表示 AJAX 请求的进度、根据状态有条件地分派操作、甚至分派操作并在之后立即检查更新的状态等场景。

¥There's no specific rule for how you should structure your actions. Using an async middleware like Redux Thunk certainly enables scenarios such as dispatching multiple distinct but related actions in a row, dispatching actions to represent progression of an AJAX request, dispatching actions conditionally based on state, or even dispatching an action and checking the updated state immediately afterwards.

一般来说,询问这些动作是否相关但独立,或者实际上应该表示为一个动作。做对你自己的情况有意义的事情,但尝试平衡 reducer 的可读性和操作日志的可读性。例如,包含整个新状态树的操作将使你的 reducer 成为单行程序,但缺点是现在你不知道为什么会发生更改,因此调试变得非常困难。另一方面,如果你在循环中发出操作以保持其粒度,则表明你可能想要引入以不同方式处理的新操作类型。

¥In general, ask if these actions are related but independent, or should actually be represented as one action. Do what makes sense for your own situation but try to balance the readability of reducers with readability of the action log. For example, an action that includes the whole new state tree would make your reducer a one-liner, but the downside is now you have no history of why the changes are happening, so debugging gets really difficult. On the other hand, if you emit actions in a loop to keep them granular, it's a sign that you might want to introduce a new action type that is handled in a different way.

尽量避免在你关心性能的地方连续多次同步调度。有许多插件和方法也可以批量调度。

¥Try to avoid dispatching several times synchronously in a row in the places where you're concerned about performance. There are a number of addons and approaches that can batch up dispatches as well.

更多信息

¥Further information

文档

¥Documentation

文章

¥Articles

讨论

¥Discussions