我有一个自定义钩子,它返回一个动作。父组件“容器”使用自定义钩子并将动作作为道具传递给子组件。
从子组件执行操作时,实际分派发生两次。现在,如果子级直接使用钩子并调用操作,则调度只发生一次。
如何复制它:
打开下面的沙盒,打开chrome上的devool,这样你就可以看到我添加的控制台日志。
https://codesandbox.io/s/j299ww3lo5?fontsize=14
主要的js(子组件)您将看到我们调用了道具。行动。getData()
在DevTools上,清除日志。在预览中,在表单上输入任意值,然后单击按钮。在控制台日志上,您将看到像redux logger这样的操作,并且您将注意到,在不更改状态的情况下执行了两次状态获取操作。
现在转到Main。js并注释掉第9行,取消注释第10行。我们现在基本上是直接使用定制钩子。
在DevTools上,清除日志。在预览中,在表单上输入任意值,然后单击按钮。在控制台日志上,现在您将看到状态_FETCHING只执行了一次,并且状态相应地发生了变化。
虽然没有明显的性能惩罚,因为它是,我不明白为什么它会发生。我可能太专注于钩子了,我错过了一些非常愚蠢的事情。。。请把我从这个谜中释放出来。谢谢
如果您使用的是React。StrictMode
,React将使用相同的参数多次调用减速机,以测试减速机的纯度。您可以禁用StrictMode,以测试减速器是否正确记忆。
从…起https://github.com/facebook/react/issues/16295#issuecomment-610098654:
没有“问题”。React故意呼叫减速机两次,以使任何意外的副作用更加明显。因为您的reducer是纯的,所以调用它两次不会影响应用程序的逻辑。所以你不应该担心这个。
在生产中,只会调用一次。
删除
首先要澄清现有的行为,STATUS_FETCHING操作实际上只是被调度(即,如果您在useApiC<--plhd--中的
)一次,但减速器代码执行了两次。getData
调用调度
之前执行console.log
1/>
如果不是因为我的研究,我可能不知道该寻找什么来解释为什么在写这个有点相关的答案时:反应钩子渲染额外的时间。
您将从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
中,您在自定义钩子中定义了减速器。这意味着在重新渲染时,您每次都提供一个新的还原器函数,即使还原器代码是相同的。除非您的减速器需要使用传递给自定义钩子的参数(而不仅仅是使用传递给减速器的state
和action
参数),否则您应该在外部级别定义减速器(即不在另一个函数中嵌套)。一般来说,我建议避免定义嵌套在另一个函数中的函数,除非它实际上使用了嵌套在其中的范围中的变量。
当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项目中,一个石英调度器,它将一次运行两次,如何修复它并一次执行一次。 和测试类, 网络初始化器类, 我的网络配置是, 在这里,我每分钟有两次“执行作业”,但我希望每分钟只有一次“执行作业”。有人能解决吗?提前谢谢!