小编典典

useReducer操作分派两次

reactjs

情境

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

问题

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

如何复制它:

打开下面的沙箱,然后在chrome上打开devtools,这样您就可以看到我添加的控制台日志。

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

Main.js(儿童组件),您将看到我们调用props.actions.getData()

在DevTools上,清除日志。在“预览”上,在表单上输入任何值,然后单击按钮。在控制台日志上,您将看到类似redux-
logger的操作,并且您将注意到STATUS_FETCHING操作被执行了两次而没有更改状态。

现在转到Main.js并注释掉第9行和第10行的注释。我们现在基本上是在直接使用自定义钩子。

在DevTools上,清除日志。在“预览”上,在表单上输入任何值,然后单击按钮。现在,在控制台日志上,您将看到STATUS_FETCHING仅执行一次,并且状态会相应更改。

虽然没有明显的性能损失,但我不明白为什么会这样。我可能过于专注于挂钩,而我却错过了一些愚蠢的事情……请把我从这个难题中解脱出来。谢谢!


阅读 738

收藏
2020-07-22

共1个答案

小编典典

为了首先阐明现有行为,实际上仅“调度”了STATUS_FETCHING操作(即,如果您在内console.logdispatch调用之前做的是正确的操作)一次,而reducer代码执行了两次。getData``useApiCall.js

写这个有点相关的答案时,我可能不知道要寻找什么来解释为什么不是我的研究目的:
您将在该答案中找到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;
  }

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

当React在重新渲染后看到新的Reducer时,它在尝试确定是否有必要进行重新渲染时不得不放弃之前所做的一些工作,因为新的Reducer可能会产生不同的结果。这只是您几乎不需要担心的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);
}

编辑useAirportsData

当化器具有要求在另一个函数中定义它的依赖项

以下是一个非常人为设计的示例,用于演示一种场景,其中在渲染过程中对reducer进行更改需要重新执行它。您可以在控制台中看到,第一次通过一个按钮触发减速器时,它将执行两次-
一次使用初始减速器(addSubtractReducer),然后再次使用其他减速器(multiplyDivideReducer)。后续分派似乎在没有先执行reducer的情况下无条件地触发了重新渲染,因此仅执行正确的reducer。如果您首先调度“
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);

编辑不同的减速器

2020-07-22