我有一个返回操作的自定义钩子。 父组件“容器”利用了自定义钩子,并将操作作为道具传递给子组件。
当从子组件执行动作时,实际调度发生两次。 现在,如果孩子直接使用该钩子并调用了该动作,则分派仅发生一次。
如何复制它:
打开下面的沙箱,然后在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仅执行了一次,并且状态相应更改。
虽然没有明显的性能损失,但我不明白为什么会这样。我可能过于关注挂钩,而我却错过了一些愚蠢的事情……请把我从这个难题中解脱出来。谢谢!
答案 0 :(得分:14)
删除<React.StrictMode>
将解决问题。
答案 1 :(得分:7)
如果您使用React.StrictMode
,React将使用相同的参数多次调用您的reducer,以测试您的reducer的纯度。您可以禁用StrictMode,以测试Reducer是否正确记忆。
来自https://github.com/facebook/react/issues/16295#issuecomment-610098654:
没有“问题”。 React故意两次调用您的reducer进行 任何意外的副作用都更加明显。由于减速器是纯净的, 两次调用它不会影响应用程序的逻辑。那么你 不必为此担心。
在生产中,它只会被调用一次。
答案 2 :(得分:6)
首先要弄清现有行为,实际上只是“派遣了” STATUS_FETCHING操作(即,如果您在{{1内的console.log
中的dispatch
调用之前执行getData
}}),但是化简代码执行了两次。
我可能不知道要寻找什么来解释为什么在撰写以下与此相关的答案时没有进行研究:React hook rendering an extra time。
您将在该答案中找到来自React的以下代码块:
useApiCall.js
特别是,请注意注释,指示React可能必须重做一些工作,如果reducer已更改。问题在于,在 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。这意味着在重新渲染时,即使化简器代码相同,您也每次都提供新的化简器功能。除非您的化合器需要使用传递给自定义钩子的参数(而不是仅使用传递给化合器的useApiCallReducer.js
和useApiCallReducer
参数),否则应在外部级别(即不嵌套在内部)定义化合器另一个功能)。通常,我建议避免避免定义一个嵌套在另一个函数中的函数,除非该函数实际上使用了嵌套函数中的变量。
当React在重新渲染之后看到新的reducer时,它必须放弃它在尝试确定是否需要重新渲染时所做的一些工作,因为新的reducer可能会产生不同的结果。这只是React代码中性能优化细节的一部分,您通常不必担心,但是值得注意的是,如果您不必要地重新定义功能,最终可能会失败一些性能优化。
为解决此问题,我更改了以下内容:
state
改为:
action
当reducer具有要求在另一个函数中定义它的依赖项(例如,对道具或其他状态)时,以下是此问题的变体的相关答案:React useReducer Hook fires twice / how to pass props to reducer?
答案 3 :(得分:5)
通过删除<React.StrictMode>
调度不会被调用多次,我也面临着这个问题,并且该解决方案有效
答案 4 :(得分:2)
严格模式无法自动为您检测副作用,但可以通过使其更具确定性来帮助您发现它们。为此,可以有意地双重调用以下功能: [...] 传递给useState,useMemo或useReducer的函数
这是因为reducer必须是纯净的,它们每次都必须使用相同的参数提供相同的输出,而React Strict Mode有时会通过两次调用reducer来自动测试(有时)。
这应该不是问题,因为这是一种仅限于开发的行为,它不会出现在生产中,所以我不建议退出<React.StrictMode>
,因为它在突出显示与以下问题有关的许多问题时会非常有帮助键,sideEffects等。