Чтобы сначала выяснить существующее поведение, действие STATUS_FETCHING фактически только «отправлялось» (т. Е. Если вы выполняете console.log
прямо перед dispatch
вызовом в getData
в useApiCall.js
) один раз, но редукторкод выполнялся дважды.
Я, вероятно, не знал бы, что искать, чтобы объяснить, почему, если бы не было моего исследования при написании этого несколько связанного ответа: React hook рендеринг дополнительноговремя .
В этом ответе вы найдете следующий блок кода из 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
пользовательского крючка.Это означает, что при повторном рендеринге вы каждый раз предоставляете новую функцию редуктора, даже если код редуктора идентичен.Если вашему редуктору не нужно использовать аргументы, передаваемые в пользовательский хук (вместо того, чтобы просто использовать аргументы 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 срабатывает дважды / как передать реквизиты редуктору?
Ниже приведен очень надуманный пример, демонстрирующий сценарий, в котором изменение редуктора во время рендеринга требует его повторного выполнения.В консоли видно, что при первом запуске редуктора с помощью одной из кнопок он выполняется дважды - один раз с исходным редуктором (addSubtractReducer), а затем снова с другим редуктором (multiplyDivideReducer).Последующие отправки, похоже, запускают повторную визуализацию безоговорочно, без предварительного выполнения редуктора, поэтому выполняется только правильный редуктор.Вы можете увидеть особенно интересное поведение в журналах, если сначала отправите действие «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);