当前位置: 首页 > 知识库问答 >
问题:

已调度两次useReducer操作

夹谷俊远
2023-03-14

我有一个自定义钩子,它返回一个动作。父组件“容器”使用自定义钩子并将动作作为道具传递给子组件。

从子组件执行操作时,实际分派发生两次。现在,如果子级直接使用钩子并调用操作,则调度只发生一次。

如何复制它:

打开下面的沙盒,打开chrome上的devool,这样你就可以看到我添加的控制台日志。

https://codesandbox.io/s/j299ww3lo5?fontsize=14

主要的js(子组件)您将看到我们调用了道具。行动。getData()

在DevTools上,清除日志。在预览中,在表单上输入任意值,然后单击按钮。在控制台日志上,您将看到像redux logger这样的操作,并且您将注意到,在不更改状态的情况下执行了两次状态获取操作。

现在转到Main。js并注释掉第9行,取消注释第10行。我们现在基本上是直接使用定制钩子。

在DevTools上,清除日志。在预览中,在表单上输入任意值,然后单击按钮。在控制台日志上,现在您将看到状态_FETCHING只执行了一次,并且状态相应地发生了变化。

虽然没有明显的性能惩罚,因为它是,我不明白为什么它会发生。我可能太专注于钩子了,我错过了一些非常愚蠢的事情。。。请把我从这个谜中释放出来。谢谢

共有3个答案

岳卓君
2023-03-14

如果您使用的是React。StrictMode,React将使用相同的参数多次调用减速机,以测试减速机的纯度。您可以禁用StrictMode,以测试减速器是否正确记忆。

从…起https://github.com/facebook/react/issues/16295#issuecomment-610098654:

没有“问题”。React故意呼叫减速机两次,以使任何意外的副作用更加明显。因为您的reducer是纯的,所以调用它两次不会影响应用程序的逻辑。所以你不应该担心这个。

在生产中,只会调用一次。

公冶渝
2023-03-14

删除

陶温书
2023-03-14

首先要澄清现有的行为,STATUS_FETCHING操作实际上只是被调度(即,如果您在useApiC<--plhd--中的getData调用调度之前执行console.log1/>)一次,但减速器代码执行了两次。

如果不是因为我的研究,我可能不知道该寻找什么来解释为什么在写这个有点相关的答案时:反应钩子渲染额外的时间。

您将从React中找到以下代码块,如该答案所示:

  var currentState = queue.eagerState;
  var _eagerState = _eagerReducer(currentState, action);
  // Stash the eagerly computed state, and the reducer used to compute
  // it, on the update object. If the reducer hasn't changed by the
  // time we enter the render phase, then the eager state can be used
  // without calling the reducer again.
  _update2.eagerReducer = _eagerReducer;
  _update2.eagerState = _eagerState;
  if (is(_eagerState, currentState)) {
    // Fast path. We can bail out without scheduling React to re-render.
    // It's still possible that we'll need to rebase this update later,
    // if the component re-renders for a different reason and by that
    // time the reducer has changed.
    return;
  }

特别是,请注意,如果减速器已更改,指示React可能必须重做某些工作的注释。问题是,在您的useApiCallReducer.js中,您在自定义钩子中定义了减速器。这意味着在重新渲染时,您每次都提供一个新的还原器函数,即使还原器代码是相同的。除非您的减速器需要使用传递给自定义钩子的参数(而不仅仅是使用传递给减速器的stateaction参数),否则您应该在外部级别定义减速器(即不在另一个函数中嵌套)。一般来说,我建议避免定义嵌套在另一个函数中的函数,除非它实际上使用了嵌套在其中的范围中的变量。

当React在重新渲染后看到新的缩减器时,它必须放弃之前在尝试确定是否需要重新渲染时所做的一些工作,因为新的缩减器可能会产生不同的结果。这只是React代码中性能优化细节的一部分,您基本上不需要担心,但值得注意的是,如果不必要地重新定义函数,您可能最终会失败一些性能优化。

为了解决这个问题,我改变了以下内容:

import { useReducer } from "react";
import types from "./types";

const initialState = {
  data: [],
  error: [],
  status: types.STATUS_IDLE
};

export function useApiCallReducer() {
  function reducer(state, action) {
    console.log("prevState: ", state);
    console.log("action: ", action);
    switch (action.type) {
      case types.STATUS_FETCHING:
        return {
          ...state,
          status: types.STATUS_FETCHING
        };
      case types.STATUS_FETCH_SUCCESS:
        return {
          ...state,
          error: [],
          data: action.data,
          status: types.STATUS_FETCH_SUCCESS
        };
      case types.STATUS_FETCH_FAILURE:
        return {
          ...state,
          error: action.error,
          status: types.STATUS_FETCH_FAILURE
        };
      default:
        return state;
    }
  }
  return useReducer(reducer, initialState);
}

改为:

import { useReducer } from "react";
import types from "./types";

const initialState = {
  data: [],
  error: [],
  status: types.STATUS_IDLE
};
function reducer(state, action) {
  console.log("prevState: ", state);
  console.log("action: ", action);
  switch (action.type) {
    case types.STATUS_FETCHING:
      return {
        ...state,
        status: types.STATUS_FETCHING
      };
    case types.STATUS_FETCH_SUCCESS:
      return {
        ...state,
        error: [],
        data: action.data,
        status: types.STATUS_FETCH_SUCCESS
      };
    case types.STATUS_FETCH_FAILURE:
      return {
        ...state,
        error: action.error,
        status: types.STATUS_FETCH_FAILURE
      };
    default:
      return state;
  }
}

export function useApiCallReducer() {
  return useReducer(reducer, initialState);
}

当减速器有依赖项(例如,依赖于道具或其他状态)需要在另一个函数中定义时,这里有一个关于此问题的变体的相关答案:React useReducer Hook fires两次/如何将道具传递给减速器?

下面是一个精心设计的示例,演示了一个场景,其中渲染期间减速器的更改需要重新执行。您可以在控制台中看到,第一次通过其中一个按钮触发减速器时,它会执行两次——一次使用初始减速器(addSubtractReducer),然后使用不同的减速器(MultilyDividedReducer)。后续调度似乎无条件地触发重新渲染,而不首先执行减缩器,因此只执行正确的减缩器。如果您首先调度“nochange”操作,您可以在日志中看到特别有趣的行为。

import React from "react";
import ReactDOM from "react-dom";

const addSubtractReducer = (state, { type }) => {
  let newState = state;
  switch (type) {
    case "increase":
      newState = state + 10;
      break;
    case "decrease":
      newState = state - 10;
      break;
    default:
      newState = state;
  }
  console.log("add/subtract", type, newState);
  return newState;
};
const multiplyDivideReducer = (state, { type }) => {
  let newState = state;
  switch (type) {
    case "increase":
      newState = state * 10;
      break;
    case "decrease":
      newState = state / 10;
      break;
    default:
      newState = state;
  }
  console.log("multiply/divide", type, newState);
  return newState;
};
function App() {
  const reducerIndexRef = React.useRef(0);
  React.useEffect(() => {
    reducerIndexRef.current += 1;
  });
  const reducer =
    reducerIndexRef.current % 2 === 0
      ? addSubtractReducer
      : multiplyDivideReducer;
  const [reducerValue, dispatch] = React.useReducer(reducer, 10);
  return (
    <div>
      Reducer Value: {reducerValue}
      <div>
        <button onClick={() => dispatch({ type: "increase" })}>Increase</button>
        <button onClick={() => dispatch({ type: "decrease" })}>Decrease</button>
        <button onClick={() => dispatch({ type: "nochange" })}>
          Dispatch With No Change
        </button>
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

 类似资料:
  • 问题内容: 情境 我有一个自定义钩子,返回一个动作。父组件“容器”利用自定义钩子并将动作作为道具传递给子组件。 问题 当从子组件执行该操作时,实际分派发生两次。现在,如果子级直接使用该钩子并调用了该动作,则分派仅发生一次。 如何复制它: 打开下面的沙箱,然后在chrome上打开devtools,这样您就可以看到我添加的控制台日志。 https://codesandbox.io/s/j299ww3l

  • 我试图通过按特定顺序调用的片段实现反向导航:

  • 问题内容: 在redux中,当调度一个动作时,reducer将相应地更改状态,调用该动作的组件也可以访问该状态(由Provider通过props传递)。我对吗? 状态是访问组件中操作结果的唯一方法吗?(已调用操作的组件)。 如何将回调函数传递给操作,然后使用该方法将结果发送回组件? 问题答案: 在redux中,当调度一个动作时,reducer将相应地更改状态,调用该动作的组件也可以访问该状态(由P

  • 我觉得有一个简单的解决办法,但由于某些原因,我不能把我的头围绕它。 我有一个情况,一个按钮必须先点击,才能点击另一个。用户为这2个JButtons选择的选项将决定程序的下一步。这意味着我必须调用actionListener两次,对吗?我如何在一个actionPerformed方法中做到这一点? 如果actionPerformed方法中的(e.getSource()==Square[1][4]&&e

  • 我有一个使用Quartz1.6.6的Java应用程序。它被部署到Weblogic上,Weblogic的体系结构包括两个应用服务器。 令人困惑的是,我有另一个Java应用程序,其中包含了Quartz调度,它似乎运行得非常愉快。另一个应用程序有一个相同的机制,每分钟触发一个触发器,从日志中我可以看到该作业每60秒只运行一次。 昨天下午作业已运行的次数示例: 15:10:46,984 15:10:49,

  • 在我的Spring MVC项目中,一个石英调度器,它将一次运行两次,如何修复它并一次执行一次。 和测试类, 网络初始化器类, 我的网络配置是, 在这里,我每分钟有两次“执行作业”,但我希望每分钟只有一次“执行作业”。有人能解决吗?提前谢谢!