当前位置: 首页 > 面试题库 >

useReducer操作分派两次

关玄裳
2023-03-14
问题内容

情境

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

问题

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

如何复制它:

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

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

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

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

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

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

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


问题答案:

为了首先阐明现有行为,实际上仅“调度”了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);
}

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

以下是一个非常人为设计的示例,用于演示一种场景,其中在渲染过程中对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);



 类似资料:
  • 我有一个自定义钩子,它返回一个动作。父组件“容器”使用自定义钩子并将动作作为道具传递给子组件。 从子组件执行操作时,实际分派发生两次。现在,如果子级直接使用钩子并调用操作,则调度只发生一次。 如何复制它: 打开下面的沙盒,打开chrome上的devool,这样你就可以看到我添加的控制台日志。 https://codesandbox.io/s/j299ww3lo5?fontsize=14 主要的js

  • 问题内容: 我有一个使用redux和react-router的通用react应用程序。 我有以下几种路线: 等等 每个路由都需要来自API的数据。目前,我在Navigation组件中的元素调度了一个async action ,该操作使用来自该路线的API中的数据填充商店。 对于MVP,我只是在路线更改时用新的帖子内容覆盖商店中的内容,这样我们就可以获取API上的所有新内容。 我已经意识到在按钮上设

  • useState 的替代方案。 接受类型为 (state, action) => newState 的 reducer,并返回与 dispatch 方法配对的当前状态。 (如果你熟悉 Redux,你已经知道它是如何工作的。) 下面例子 useState 部分的计数器示例,重写为使用 reducer: import { useReducer } from 'rax'; const initialS

  • buffer buffer() 操作符的函数签名: buffer([breakObservable]) buffer 本身意味着我们在等待而不会发出任何值,直到 breakObservable 发生。示例如下: let breakWhen$ = Rx.Observable.timer(1000); let stream$ = Rx.Observable.interval(200) .buffer(

  • $ git branch 添加-a选项,就可以显示包括远端分支在内的分支清单。 创建分支 $ git branch <branchname> 高级篇 【教程1 操作分支】 1. 建立分支 修改分支的名称 $ git branch -m <oldbranch> <newbranch> 删除分支 $ git branch -d <branchname> 若有未合并到HEAD的提交,则不能删除分支。如果

  • 问题内容: 在人们的帮助下,我能够获得以下简单的GUI倒数的工作代码(它仅显示一个倒数秒的窗口)。我的这段代码的主要问题是东西。 据我了解,它将任务发送到事件分发线程(EDT),然后EDT在“可以”时执行该任务(无论如何)。 那正确吗? 据我了解,代码的工作方式如下: 在方法中,我们用来显示窗口(方法)。换句话说,显示窗口的代码将在EDT中执行。 在该方法中,我们还启动,计数器(通过构造)在另一个