【问题标题】:Using Globalized Redux Selector In Side Effect Creates Circular Dependency在副作用中使用全球化 Redux 选择器会创建循环依赖
【发布时间】:2023-09-07 18:57:01
【问题描述】:

在使用normalizr 对其进行规范化后,我将表单数据存储在 redux 存储中。当我提交表单时,我使用 thunk 中的选择器获取非规范化数据,然后将其发送到服务器。流程如下:

rootReducer -> localReducer -> action/actionCreator -> rootReducer

rootReducer 文件中,根reducer 组成localReducer 并包含稍后在thunk 中使用的全局选择器。 localReducer 文件从包含动作创建者的动作文件中导入动作。 thunk 动作创建者返回一个 thunk,它使用 rootReducer 文件中的选择器检索到的数据执行 api 调用,因此是循环依赖。

Webpack 不能很好地处理这种循环依赖。我在localReducer -> action/actionCreator 级别遇到了运行时Uncaught TypeError: Cannot read property 'JOB_FORM_RESET' of undefined 错误:

const jobsForm = (state = initialState, action) => {
  switch (action.type) {
    case ActionTypes.JOB_FORM_RESET:

关于如何解决这个问题的任何想法?

编辑

ActionTypes 被评估为undefined 按规定工作。 ActionTypes 位于action/actionCreator 文件中,当它第一次被localReducer 导入时,它的执行没有完成,因为它立即开始导入rootReducer。为了避免无限循环,action/actionCreator(其中ActionTypes 被评估为undefined)的未完成副本被提供给localReducer

解决方案是将动作和动作创建者放在两个不同的文件中,从而将它们分开。这将删除循环依赖,如以下流程所示:

rootReducer -> localReducer -> action
actionCreator -> rootReducer

对我来说奇怪的是,长期以来,redux 指南中一直提倡将动作和动作创建者分组,而将它们分成两个文件感觉不太自然。

另外,redux-saga 模型中没有出现这个循环问题:

rootReducer -> localReducer -> action/actionCreator
saga -> api -> rootReducer

我习惯了这个模型,但不敢相信redux-thunk 模型不能解决这个问题。换句话说,说循环问题是redux-thunk 模型的固有副作用似乎并不公平。我在这里遗漏了什么吗?

您可以在此repo 中找到 MCVE。报错不同但原理相同,都是src/Users/actions.js文件中的如下导入引起的循环依赖引起的:
import { getSelectedUsers } from '../reducer';

发生的错误是No reducer provided for key "users"。只需注释上面的导入,错误就会消失。

正如我上面所描述的,这可以正常工作,我担心redux-thunk 模型不能处理这个用例。此外,将动作和动作创建者放在同一个文件中,然后等待出现循环依赖问题来将它们分开似乎不是一个可扩展的解决方案。

【问题讨论】:

  • 将选择器拆分为单独的文件,以便 thunk 可以导入该文件而不是 rootReducer?
  • 我正在遵循一个非常著名的 redux 指南,其中全球化选择器是被导入和使用的选择器(本地化选择器用于链中)。理论上这应该可行,我想继续使用它而不尝试解决方法。
  • 您能否提供一个最低限度的可验证示例*.com/help/mcve 而不是写您的代码如何运行?它可以为读者节省大量的认知周期,并提高响应速度和质量。
  • @KarenGrigoryan 完成。

标签: reactjs redux react-redux redux-thunk redux-saga


【解决方案1】:

解决方法很简单:将actionTypes提取到单独的文件中,并在actions.jsreducer.js中导入

actionTypes.js文件:

export const SELECT_USER = 'SELECT_USER'; export const POST_USERS = 'POST_USERS';

您可以像这样一次导入所有操作

import * as actionTypes from './actionTypes.js'

问题在这里解决了:

https://github.com/svitekpavel/redux-thunk-globalized-selectors-cyclic-dependencies/commit/1c7f04fc5c1d4e4155891428138f8cb00412655e

另外两条建议:

  1. 将选择器提取到单独的文件中
  2. 将“效果”(postUsers) 提取到 effects.js

第二个建议来自经验,redux-thunks 的教程保留在actions.js 中的这些功能(副作用)实际上是副作用而不是动作创建者。

如果您使用 Redux-Saga,您会很快意识到将业务逻辑(和副作用)与动作创建者分离是一件好事。

另外,它们是两个不同的东西:-)

【讨论】:

  • 我已经提到了你在我的问题帖子中描述的解决方案,并且还提到使用redux-saga时不存在该问题。我关心的是在使用 redux-thunk 时如何避免这个问题。更多细节在问题帖中。
  • to separate them doesn't seem a scalable solution => 这到底是什么意思?
  • 从一开始就分离动作类型和动作创建者很容易。这不是一开始就放在同一个文件里的情况,代码进化了很多,然后我们决定把它们分开。它需要大量的重构,尤其是如果有很多现有的引用。