본문 바로가기
Front-End/React

[React] 리엑트 컴포넌트가 두 번 렌더링 되는 이유?

by SeanK 2022. 7. 29.

이번 스프린트를 마치기 전 데모를 해보다가 화면상 표시되는 데이터가 실제 데이터와 다르다는 사실을 발견했다. 

원인을 찾아보니 동료 개발자가 의도치 않게 자료형에 Mutation을 일으켜 잘못된 데이터가 표시된 것이었다. 

 

그리고 Mutation이 발생한 이유는 2회 컴포넌트가 렌더링 되면서 함수가 두 번 실행되었기 때문이라는 사실도 알게 되었다. 

 

사실 이전 부터 궁금하긴 했었다. useState 혹은 useEffect를 사용한 컴포넌트에서 콘솔을 찍으면 항상 두 번 프린트되었기 때문이다. 그래서 오늘은 관련해서 왜 그런지 찾아보았고 재밌는 글을 발견해 번역해 옮겨 본다.

My React components render twice and drive me crazy

모던 리엑트를 사용하는 수 많은 프론트엔드 개발자들은 때때로 왜 컴포넌트가 개발환경에서 두 번 렌더링 되는지 이유를 찾으며 머리를 쥐어 뜯은 경험이 있을 것이다. 
이러한 현상을 발견한 몇몇 개발자들 중 일부는 그냥 리엑트가 이렇게 작동한다고 믿었고 일부는 리엑트 공식 리포지토리에 버그 신고를 하기도 했다. 
커뮤니티 내에서 관련해서 혼란이 있는 것은 분명해 보인다. 
이런 현상이 발생하는 이유는 React.StrictMode 때문이다. 
동일한 현상을 일으키는 예제를 살펴보고 왜 이런 현상이 발생하는지 한번 알아보자. 

 

# Example with a function component

새로운 CRA 설치를 우선 하자:

npx create-react-app my-app && cd my-app
 
App.js에 간단한 console.log 코드를 추가해 주자:
function App() {
  console.log('I render 😁');

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
      </header>
    </div>
  );
}
그리고 나서 어플을 yarn start로 실행 시키면 브라우저 http://localhost:3000에서 확인이 가능하다:
음... I render 콘솔이 한번만 찍혔다. 따라서 더블 렌더링을 간단한 콘솔 코드로는 확인 할 수 없었다. 

 

# Example with a function component with state

하지만 함수형 컴포넌트에 이렉트 훅을 사용해 상태관리를 한다면 어떻게 될까?

function App() {
  const [clicks, setClicks] = React.useState(0);

  console.log('I render 😁');

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />

        <button onClick={() => setClicks(clicks => clicks + 1)}>
            Clicks: {clicks}
        </button>
      </header>
    </div>
  );
}
브라우저에서 다시한번 확인해 보자:

성공했다!! 첫번째 로딩에서 두 번 렌더링 되었고 매번 클릭할 때마다 두 번 콘솔이 찍힌다. 

 

# Example with a function component with state in production

Production 번들에서는 어떨까? 이를 확인하기 위해, 어플리케이션을 빌드해야한다. 그리고 나서 포트 3000에서 빌드된 어플리케이션을 제공해보자. 
yarn build && npx serve build -l 3000
 
브라우저에서 http://localhost:3000을 다시 한번 오픈해 보자:
 

휴!! 디버깅에서 1회만 프린트 했고 버튼을 클릭해도 마찬가지다. 
더블 렌더링 현상은 똑같은 코드를 사용했음에도 불구하고 프로덕션 환경에서는 재현할 수 없었다.

 

# Why this is happening?

위에서 언급했듯 이러한 현상은 React.StrictMode 때문이다. src/index.js 파일을 살펴보면 <App /> 컴포넌트가 아래와 같이 래핑 되어 있는 것을 확인할 수 있다. 

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);
사실 리-렌더링 현상은 버그가 아니고 라이브러리의 렌더링 메커니즘 또한 아니다. 오히려 이는 리엑트에서 제공하는 디버깅 메커니즘이라 할 수 있다. 
 

# What is React.StrictMode?

React.StrictMode는 2018년에 16.3.0 버전에서 소개되었다. 처음에는 클래스 컴포넌트에만 적용가능 했지만 16.8.0 이후로는 훅에서도 이용 가능해졌다. 

배포 노트에 의하면:

React.Strict Mode는 어플의 비동기적 렌더링 준비를 도와주는 wrapper이다. 

따라서 이것의 목적은 엔지니어가 흔히 빠질 수 있는 함정을 피하도록 돕고 레거시 API를 점차적으로 드랍시켜 리엑트 어플의 업그레이드를 돕는 데 있다. 

라이브러리가 비동기적 렌더링의 시대로 나아가는 상황에서 많은 변화가 일어나기 때문에 이런 힌트는 디버깅에 아주 큰 도움이 된다. 유용하지 않은가?

 

# Why the double rendering then?

React.StrictMode의 사용으로부터 얻을 수 있는 장점으로는 렌더단계의 라이프사이클에서 예상치 못했던 사이드 이펙트를 발견하는데 도움을 준다. 

라이프 사이클로는:

  • constructor
  • componentWillMount (or UNSAFE_componentWillMount)
  • componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
  • componentWillUpdate (or UNSAFE_componentWillUpdate)
  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • setState updater functions (the first argument)

위 모든 메소드들이 두 번 이상 호출된다. 따라서 내부에 사이드 이펙트가 없도록 하는 것이 중요하다. 만약에 이러한 원칙을 무시한다면 일정하지 않은 상태 문제와 메모리 누수를 겪게 될 것이다. 

React.StrictMode는 사이드 이펙트를 단번에 찾아낼 수는 없지만 몇몇 중요한 함수를 고의로 두 번 실행시켜 이를 찾기 쉽도록 해준다. 

 

그러한 함수로는:

  • Calss component constructor, render, shouldComponentUpdate 메소드
  • Class component static getDerivedStateFromProps 메소드
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, 혹은 useReducer

리-렌더링은 분명히 성능에 영향을 미친다. 하지남 이는 개발환경에서만 발생하고 프로덕션에서는 발생하지 않으니 걱정하지 말라. 

 

 

 

 

출처: https://mariosfakiolas.com/blog/my-react-components-render-twice-and-drive-me-crazy/

 

My React components render twice and drive me crazy

Many frontend developers who use modern React, wonder why their components render twice during development but this is actually happening for a good reason.

mariosfakiolas.com