React Strict Mode에서의 useReducer

tyange·2023년 10월 24일

사내 프로젝트를 CRA 기반에서 Vite 기반으로 migration 하는 과정에서, 분명 같은 코드인데 로컬 개발 환경의 실행 결과와 빌드한 결과물의 실행 결과가 나오는 이상한 현상이 있었다.

주로 useEffectuseState, useReducer가 두 번씩 실행되는 현상이었다. 처음에는, 왜 이런 현상이 일어나는지 몰라서 어리둥절 하기만 했다.

도저히 안되겠다 싶어서 집에서 비슷한 프로젝트를 만들어 크롬 개발자 도구에서 디버거를 걸었다. 코드의 실행 흐름을(React 내부에서 어떤 실행 흐름을 가지는지 알 수 있었다...) 따라가다 보니, 'strictMode' 라는 단어가 눈에 띄었다. 잊은 게 있었다. 이전 프로젝트는 React의 strict mode를 '강제로 끈' 상태의 프로젝트였다는 것을... Vite로 migration을 진행하면서, 이 strict mode도 다시 생겨난 것이었다.

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <React.StrictMode>
      <App />
  </React.StrictMode>
);

vite 명령어로 프로젝트를 생성했다면, React의 Strict Mode는 main.tsx 파일의 일종의 wrapper 컴포넌트로 위치한다. 위 코드에서 볼 수 있듯이 <App /> 컴포넌트를 감싸고 있는 형태다.

이렇게 되면 React 프로젝트의 개발 환경은 Strict Mode로 실행이 되며 해당 Strict Mode 컴포넌트가 감싸고 있는 모든 컴포넌트는 Strict Mode 검사가 이루어진다.

이 Strict Mode 실행 환경에서 내가 제일 난감했던 것은 이전에 문제 없이 실행이 되었던(문제 없이 실행된다고 믿었던) useReducer 훅이 제대로 작동하지 않는 현상이었다.
이 현상은 useReducerreducer에서 특정 구문이 실행될 때마다 한 번이 아닌 두 번씩 실행되는 현상이었다. 두 번씩 실행되기 때문에 내가 원하는 동작을 구현해 놓아도, 그 동작 이후에 다시 동작을 실행하여 결과적으로 초기값으로 되돌아가고 있었다.

이 에러의 근원을 찾기 위해 구글링을 했는데, 나와 비슷한 문제를 겪은 사람이 만든 Github 이슈가 있었다.
이 이슈의 내용과 댓글을 살펴 보니 Strict Mode에서 useReducer 훅이 두 번 실행되는 현상은 reducer가 '순수'하지 않기 때문이라는 걸 알 수 있었다.
여기서 '순수하지 않다'의 뜻은 쉽게 풀어보자면 reducer 내부 코드에서 관리되는 state를, state 그 자체로 조작하는 것을 말한다.

const counterReducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return state.count + 1;
    case "DECREMENT":
      return state.count - 1;
    case "RESET":
      return 0;
    default:
      break;
  }
};

위와 같은 코드가 state를 그대로 조작하는 reducer 코드다. state에 그대로 1을 더하거나 1을 빼고 있다. 위 코드는 아래와 같이 수정하여 '순수'하게 만들 수 있다.

const counterReducer = (state, action) => {
  switch (action.type) {
    case "INCREMENT":
      return { 
        ...state,
        count: state.count + 1
      };
    case "DECREMENT":
      return { 
        ...state,
        count: state.count - 1
      };
    case "RESET":
      return { count: 0 };
    default:
      break;
  }
};

위의 코드처럼 reducer를 순수한 함수로 만드려면, 조작한 state 값을 완전히 다시 할당해주어야 한다. 이렇게 코드를 바꾸고 나니 useReducer가 더는 두 번 실행되지 않았다.

profile
아주 흐린 날의 기록

0개의 댓글