我有一个返回动作的自定义钩子。父组件“Container”使用自定义钩子并将操作作为prop传递给子组件。
从子组件执行操作时,实际的分派会发生两次。现在,如果孩子直接使用钩子并调用该动作,则只发生一次调度。
如何重现它:
打开下面的沙箱并在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仅执行一次,状态也相应地更改。
虽然没有明显的性能惩罚,但我不明白为什么会发生这种情况。我可能太专注于胡克斯而且我错过了一些如此愚蠢的东西......请让我从这个谜题中解脱出来。谢谢!
首先澄清现有的行为,STATUS_FETCHING动作实际上只是“调度”(即如果你在console.log
dispatch
中getData
之前调用useApiCall.js
),但减速器代码执行了两次。
我可能不知道要解释为什么如果在编写这个有点相关的答案时我的研究没有:React hook rendering an extra time。
您将在该答案中找到以下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
中,你在useApiCallReducer
定制钩子里面定义了你的减速器。这意味着在重新渲染时,每次都提供一个新的reducer函数,即使reducer代码相同。除非你的reducer需要使用传递给自定义钩子的参数(而不是仅使用传递给reducer的state
和action
参数),否则你应该在外层定义reducer(即不嵌套在另一个函数中)。一般来说,我建议避免定义嵌套在另一个函数中的函数,除非它实际使用嵌套在其中的作用域中的变量。
当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具有依赖关系(例如,在props或其他状态上)需要在另一个函数中定义时,对此问题的变体的相关答案:React useReducer Hook fires twice / how to pass props to reducer?