Skip to main content

操作(Actions)

Redux 常见问题:操作(Actions)

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

与状态一样,可序列化的操作支持 Redux 的几个核心特性,比如时间旅行调试,以及记录和重放操作。如果 type 使用类似 Symbol 的类型或对操作本身使用 instanceof 检查,就会破坏这些功能。字符串是可序列化且自描述的,因此是更好的选择。请注意,如果操作旨在由中间件使用,操作中使用 Symbols、Promises 或其他不可序列化的值也是可以的。操作只需在真正到达 store 并传递给 reducer 时是可序列化的。

出于性能考虑,我们无法可靠地强制所有操作都可序列化,所以 Redux 仅检查每个操作必须是一个普通对象,且 type 是字符串。其余由你自己决定,但你可能会发现保持一切可序列化有助于调试和重现问题。

封装并集中常用代码片段是编程的关键概念。虽然确实可以在任何地方手动创建操作对象并手写每个 type 字符串,但定义可复用的常量会使代码维护更便捷。如果把常量放到单独的文件中,还可以通过工具检查导入语句中的拼写错误,防止意外使用错误的字符串。

更多信息

文档

讨论

reducer 和操作之间是否总是一一对应的?

不是。我们建议编写独立的小型 reducer 函数,每个 reducer 负责特定状态片段的更新。我们称这种模式为“reducer 组合”。一个操作可以被全部、部分或无任何 reducer 处理。这样保持组件与实际数据变动解耦,因为一个操作可能影响状态树的不同部分,组件无需知道这些细节。一些用户会选择更紧密地绑定它们,比如“ducks” 文件结构,但默认情况下绝不存在一一对应关系。如果你发现需要在多个 reducer 中处理同一个操作,应该跳出一一对应的思维模式。

更多信息

文档

讨论

如何表现 “副作用” 例如 AJAX 请求?为什么需要 “操作创建者”、“thunks” 和 “中间件” 来处理异步行为?

这是一个复杂且意见多样的话题,关于代码应如何组织以及应采用何种方案各家观点不一。

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

Redux 受到函数式编程的启发,开箱即用时没有执行副作用的方案。具体来说,reducer 函数必须始终是纯函数,形式为 (state, action) => newState。但 Redux 的中间件可以拦截派发的操作并在周围添加复杂行为,包括副作用。

通常,Redux 建议把副作用代码放进操作创建流程中。尽管这些逻辑可以写在 UI 组件中,但通常更合理抽象成可复用函数,这样同一逻辑可被多处调用,也就是操作创建者函数。

最简单且常用的做法是添加 Redux Thunk 中间件,允许你编写包含更复杂异步逻辑的操作创建者。另一种广泛使用的方案是 Redux Saga,它使用 generator 函数编写看似同步的代码,能够表现像 Redux 应用中的“后台线程”或“守护进程”。还有一种方案是 Redux Loop,它通过倒置流程,使 reducer 在响应状态变化时声明副作用,由其他机制执行。除此之外,社区还开发了许多其他库和思路,每个都有自己对副作用管理的看法。

更多信息

文档

文章

讨论

应该使用哪种异步中间件?如何在 thunks、sagas、observables 或其他方案间做选择?

许多可用的异步/副作用中间件,但最常用的是 redux-thunkredux-sagaredux-observable。这些工具各有特长、弱点和适用场景。

通用建议:

  • Thunks 适合复杂的同步逻辑(尤其是需要读取整个 Redux store 状态的代码)和简单异步逻辑(如基本 AJAX 请求)。借助 async/await,thunks 也能胜任部分较复杂的 Promise 逻辑。
  • Sagas 适合复杂异步逻辑和解耦的“后台线程”类型行为,尤其是在需要监听派发操作时(这是 thunks 做不到的)。使用时需要熟悉 generator 函数及 redux-saga 的“effects”操作符。
  • Observables 与 sagas 解决同样的问题,但依赖 RxJS 来实现异步行为。使用时需熟悉 RxJS API。

我们建议大多数 Redux 用户从 thunks 开始使用,只有在应用确实需要更复杂的异步逻辑处理时,才添加 sagas 或 observables 等副作用库。

因为 sagas 和 observables 适用场景相同,一个应用一般选其一,而不会两者兼用。但注意,使用 thunks 以及 sagas 或 observables 其一是完全可以共存的,因为它们解决的是不同的问题。

文章

讨论

我应该在一个操作创建者中连续派发多个操作吗?

对于操作的结构没有硬性规定。像 Redux Thunk 这样的异步中间件当然支持连续派发多个相关但不同的操作,派发表示 AJAX 请求进度的操作,根据状态条件派发操作,甚至派发某个操作后立即检查更新的状态。

一般来说,考虑这些操作是相关但独立的,还是应该合并为一个操作。根据你的具体情况做决定,但要权衡 reducer 的可读性和操作日志的可读性。例如,一个包含整个新状态树的操作会让 reducer 变成一行代码,但缺点是你失去了变化原因的历史记录,调试会非常困难。反之,如果你通过循环发送多个细粒度操作,那可能意味着你应该引入一种新的操作类型,并通过不同方式处理。

如果你关心性能,尽量避免在同一时刻连续同步派发多次操作。有许多插件和方案可以对 dispatch 操作进行合并批处理。

更多信息

文档

文章

讨论