【问题标题】:Infinite useEffect loop when using Redux with React Hooks使用 Redux 和 React Hooks 时的无限 useEffect 循环
【发布时间】:2019-04-05 15:56:10
【问题描述】:

我正在重构一些代码并将我的类组件转换为函数组件,以此作为学习如何使用 Hooks 和 Effects 的一种方式。我的代码使用 Redux 进行状态管理,使用 axios 进行数据库请求,使用 Thunk 作为中间件来处理异步性。我在一个组件中遇到问题,该组件执行获取请求以检索以前为componentDidMount 的客户列表。无论我尝试什么,useEffect 函数都会进入无限循环并继续请求客户列表。

有问题的组件CustomersTable 从数据库中获取客户列表并将其显示在表格中。该组件由一个容器组件包装,该组件使用 Redux 的 connect 将检索到的客户列表作为道具传递给 CustomersTable

useEffect(() => {
    loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
  }, []);

loadCustomers 是一个 Redux 操作,它使用 axios 来获取客户列表。 currentPage、itemsPerPage、sortProp 和ascending 是状态变量,在“组件挂载”上初始化为特定值

我希望因为我使用空数组,它只会运行一次。相反,它连续运行。我无法弄清楚为什么会这样。我最好的猜测是,当 redux 获取列表时,它会返回一个新的状态对象,因此 props 每次都会更改,然后触发重新渲染,然后获取一个新列表。我是不是用错了,因为 Redux 不应该与这样的钩子一起使用?

我最终通过添加以下内容来完成这项工作:

useEffect(() => {
    if (!list.length) {
      loadCustomers(currentPage, itemsPerPage, sortProp, (ascending ? 'asc' : 'desc'), {});
    }
  }, []);

我不确定这是我真正想要的行为。如果客户列表确实为 0,那么代码将继续获取该列表。如果列表真的是空的,那么我希望它只获取一次然后停止。编辑:事实证明这绝对行不通。它适用于初始加载,但会破坏任何删除或编辑的代码。

好的,这里提供更多上下文。包装了 CustomersTable 的容器组件是:

import { connect } from 'react-redux';
import loadCustomers from './actions/customersActions';
import { deleteCustomer } from './actions/customerActions';
import CustomersTable from './CustomersTableHooks';

function mapStateToProps(state) {
  return {
    customers: state.customers,
    customer: state.customer
  };
}

export default connect(mapStateToProps, { loadCustomers, deleteCustomer })(CustomersTable);

动作,loadCustomers 是:

export default function loadCustomers(page = 1, itemsPerPage = 50, sortProp = 'id', sortOrder = 'asc', search = {}) {
  return (dispatch) => {
    dispatch(loadCustomersBegin());
    return loadCustomersApi(page, itemsPerPage, sortProp, sortOrder, search)
      .then(data => dispatch(loadCustomersSuccess(data)))
      .catch(() => dispatch(loadCustomersFailure()));
  };
}

客户的减速机是:

export default function customersReducer(state = initialState, action) {
  switch (action.type) {
    case types.LOAD_CUSTOMERS_BEGIN:
      return Object.assign({}, state, { isLoading: true, list: [], totalItems: 0 });
    case types.LOAD_CUSTOMERS_SUCCESS:
      return Object.assign({}, state, { isLoading: false, list: action.customers || [], totalItems: action.totalItems });
    case types.LOAD_CUSTOMERS_FAILURE:
      return Object.assign({}, state, { isLoading: false, list: [], totalItems: 0 });
    default:
      return state;
  }
}

很遗憾,我不能发布太多 CustomersTable 本身的内容,因为这些内容的命名方式可以告诉您我在为哪家公司工作。

【问题讨论】:

  • 上面的代码似乎是正确的。您可以发布创建可重现演示的整个组件
  • 这是工作代码,所以很遗憾我不能发布整个组件,也不能发布演示。
  • 没有必要按原样发布您的代码,我们只需要一个类似于您的虚拟设置来重现问题
  • 嗯,我必须发布很多代码来显示类似的设置,包括所有 Redux 代码。我想我是想问一个更理论的问题。由于我没有将客户列表加载到状态中,而是将其加载到 Redux 状态然后将其作为道具传递,我是否会导致无限循环,因为这会注册为更改道具并因此重新渲染?
  • 你是对的,但是某些 useEffect 是通过第二个参数空数组传递的,它告诉 react 它应该只运行一次,所以这里可能有其他代码在工作中导致问题,所以我们需要看看更多上下文。

标签: reactjs redux react-hooks


【解决方案1】:

所以,如果我正确理解您的代码,您将在 useEffect 内的子组件中调度 loadCustomers 操作,但您在父母 mapStateToProps 中读取实际数据。

当然,这会创建无限循环:

  1. 父母从商店中读取customers(或者从商店中读取任何东西,就此而言)
  2. 渲染儿童
  3. child 在useEffect 中获取customers
  4. 父属性更改并导致重新渲染
  5. 整个故事永远继续下去

故事的寓意:不要 dispatch 来自演示组件。或者,换句话说,dispatch 来自您从存储中读取相同属性的同一组件的操作。

【讨论】:

  • 我发现很难在脑海中描绘出什么在更新什么,所以这个答案真的帮助了我,解决了我的问题。
【解决方案2】:

在每次渲染时,您都会获得新的客户对象,因为 mapStateToProps 如果执行浅相等。您可以使用记忆选择器来获取客户,并且在不需要时不会重新呈现。

import { createSelectorCreator, defaultMemoize } from 'reselect';

const createDeepEqualSelector = createSelectorCreator(defaultMemoize, deepEqual);

const customerSelector = createDeepEqualSelector(
 [state => state.customerReducer.customers],
 customers => customers,
);

【讨论】:

    【解决方案3】:

    我不同意这里投票最多的答案。

    1. 父属性更改并导致重新渲染
    2. 整个故事永远继续下去

    当依赖项的第二个参数为空数组时,重新渲染不会调用useEffect 的函数参数。这个函数只会被第一次调用,类似于生命周期方法componentDidMount。似乎创建了这个主题是因为没有发生正确预期的行为。

    似乎正在发生的事情是组件正在卸载然后再次安装。我遇到了这个问题,它把我带到了这里。不幸的是,没有组件代码,我们只能猜测实际原因。我以为它与react-redux connect 有关,但事实证明并非如此。就我而言,问题是有人在组件中有一个组件定义,并且在每次渲染时都重新定义/重新创建了该组件。这似乎是一个类似的问题。我最终将该嵌套定义包装在 useCallback 中。

    【讨论】:

      猜你喜欢
      • 2019-08-15
      • 1970-01-01
      • 2021-05-17
      • 2021-09-07
      • 1970-01-01
      • 1970-01-01
      • 2021-04-28
      • 2019-10-25
      • 2021-01-31
      相关资源
      最近更新 更多