programing

useReducer 액션이 두 번 디스패치되었습니다.

subpage 2023. 3. 15. 19:36
반응형

useReducer 액션이 두 번 디스패치되었습니다.

시나리오

작업을 반환하는 사용자 지정 후크가 있습니다.상위 구성 요소 "컨테이너"는 사용자 지정 후크를 사용하여 작업을 하위 구성 요소에 소품으로 전달합니다.

문제

하위 구성 요소에서 작업을 실행하면 실제 디스패치가 두 번 수행됩니다.이제 자녀가 직접 후크를 사용하여 액션을 호출한 경우 디스패치는 한 번만 실행됩니다.

재생 방법:

아래 샌드박스를 열고 chrome에서 devtools를 열면 추가한 콘솔 로그를 볼 수 있습니다.

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

Main.js(자녀 컴포넌트)는 propos.actions.getData()를 호출합니다.

DevTools에서 로그를 지웁니다.미리보기에서 폼에 값을 입력하고 버튼을 클릭합니다.콘솔 로그에는 redex-logger 등의 액션이 표시되며 상태를 변경하지 않고 STATUS_FETCHING 액션이 두 번 실행된다는 것을 알 수 있습니다.

Main.js로 이동하여 9행과 10행의 코멘트를 입력합니다.현재는 기본적으로 커스텀 훅을 직접 소비하고 있습니다.

DevTools에서 로그를 지웁니다.미리보기에서 폼에 값을 입력하고 버튼을 클릭합니다.콘솔 로그에는 STATUS_FETCHING이 한 번만 실행되어 그에 따라 상태가 변경됩니다.

명백한 성능 불이익은 없지만, 왜 그런 일이 일어나는지 이해할 수 없습니다.훅스에 너무 집중해서 바보 같은 걸 놓쳤나 봐이 퍼즐에서 날 풀어줘감사합니다!

기존 동작을 하기 dispatched)되어 (즉, STATUS_FETCHING을 ).되었을 뿐입니다(즉,console.log의 the dispatchgetData의 범위 내에서useApiCall.js)가 1회 실행되었지만 리듀서 코드가 2회 실행되었습니다.

이 다소 관련이 있는 답변을 쓸 때 내 연구가 아니었다면, 는 무엇을 찾아야 할지 몰랐을 것이다: 리액트 후크 렌더링(react hook rendering) 추가 시간.

이 답변에 표시된 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 ★★★★★★★★★★★★★★★★★」actionreducer에 전달되는 인수), 외부 수준에서 reducer를 정의해야 합니다(즉, 다른 함수 내부에 중첩되지 않음).일반적으로 함수가 내포된 범위의 변수를 실제로 사용하지 않는 한 다른 함수에 내포된 함수를 정의하는 것을 피하는 것이 좋습니다.

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);
}

use Airports Data 편집

다음은 환원기가 다른 기능 내에서 정의되어야 하는 종속성(예: 지지대 또는 기타 상태에 대한)이 있을 때 이 문제에 대한 변동에 대한 관련 대답이다.리액션 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);

다양한 리듀서 편집

를제 remove remove remove를 remove remove 를 remove 。<React.StrictMode>문제가 해결됩니다.

★★★★★★★★★를 사용하고 있는 경우는React.StrictModeReact는 환원제의 순도를 테스트하기 위해 동일한 인수를 사용하여 환원제를 여러 번 호출합니다.Strict Mode를 디세블로 하면 리듀서가 올바르게 메모되어 있는지 테스트할 수 있습니다.

https://github.com/facebook/react/issues/16295#issuecomment-610098654 에서 :

"문제"는 없습니다.리액션은 예상치 못한 부작용을 더 명확하게 하기 위해 의도적으로 당신의 환원제를 두 번 호출합니다.리듀서는 순수하기 때문에 두 번 불러도 어플리케이션의 로직에는 영향을 주지 않습니다.그러니까 걱정하지 마세요.

프로덕션에서는 한 번만 호출됩니다.

React 문서의 설명대로:

strict 모드는 부작용을 자동으로 검출할 수 없지만, 조금 더 결정적으로 만들어 부작용을 검출하는 데 도움이 됩니다.이 작업은 의도적으로 다음 함수를 이중 호출하여 수행됩니다. [...] useState, useMemo 또는 useReducer로 전달된 함수

이는 리덕터가 순수해야 하며 매번 동일한 인수로 동일한 출력을 제공해야 하며 리액트 엄격 모드에서는 리덕터를 두 번 호출하여 자동으로 테스트(경우에 따라)하기 때문입니다.

이것은 개발에만 한정되는 행동이기 때문에 문제될 것이 없기 때문에 그만두는 것은 추천하지 않습니다.<React.StrictMode>키, sideEffects 등과 관련된 많은 문제를 강조하는데 매우 도움이 되기 때문입니다.

제거함으로써<React.StrictMode>디스패치는 여러 번 호출되지 않습니다.저도 이 문제에 직면해 있었기 때문에 이 솔루션은 효과가 있었습니다.

이것은,<React>를 삭제해 주세요.Strict Mode > 디스패치는 여러 번 호출되지 않습니다.저도 이 문제에 직면해 있었기 때문에 이 솔루션은 효과가 있었습니다.

언급URL : https://stackoverflow.com/questions/54892403/usereducer-action-dispatched-twice

반응형