When React re-renders

혜진 조·2022년 12월 28일
1

리액트

목록 보기
22/31
post-custom-banner

본 포스트는 https://www.joshwcomeau.com/react/why-react-re-renders/
를 읽고 정리한 글입니다.

리액트는 언제 리렌더링 되는가?

코드

import React from 'react';

function App() {
  return (
    <>
      <Counter />
      <footer>
        <p>Copyright 2022 Big Count Inc.</p>
      </footer>
    </>
  );
}

function Counter() {
  const [count, setCount] = React.useState(0);
  
  return (
    <main>
      <BigCountNumber count={count} />
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </main>
  );
}

function BigCountNumber({ count }) {
  return (
    <p>
      <span className="prefix">Count:</span>
      {count}
    </p>
  );
}

export default App;

이 예시 코드에서 3개의 컴포넌트가 존재한다. 최상위 컴포넌트인 App에서 Counter가 렌더링 되면 자식 컴포넌트인 BigCountNumber 가 렌더링 된다.
count의 상태가 변할때마다 Counter컴포넌트는 리렌더되고, Counter 컴포넌트에 의해 BigCountNumber가 리렌더된다.

🤔 오해 1. state가 변할 때마다 전체 컴포넌트가 리렌더링 된다?! ❌

Re-render는 오직 해당 state를 포함하고 있는 컴포넌트와 그 자식 컴포넌트에만 영향을 미친다. 그러므로 위 예시코드에서 counter라는 state가 변하더라도 App 컴포넌트는 리렌더링 되지 않는다.

리액트의 주업무는 어플리케이션 UI를 리액트 state에 맞춰 동기적으로 유지하는 것이다.
리렌더의 핵심은 무엇이 바뀌어야 하는지를 파악하는 것!
각각의 렌더링은 스냅샷과 같다(현재 어플리케이션의 state에 기초한).
리액트는 두 개의 스냅샷을 비교하여 다른점을 찾아내는 "다른점을 찾아내는 게임"이라고 생각해도 좋다. 예시코드 Counter컴포넌트에서 사용자가 버튼을 클릭해 count state가 0 에서 1이 되었고, 리액트는 이전 스냅샷과 현 스냅샷을 비교해 0에서 1로 바뀐 텍스트 노드를 수정한다.

리액트는 단방향 데이터 흐름(one-way reactive data flow)을 갖기 때문에,
Counter컴포넌트의 count의 상태가 변하더라도 상위 컴포넌트인 App 컴포넌트에 영향을 주지 않는다. 즉 리렌더링 하지 않는다. 반면 같은 논리로 자식 컴포넌트인 BigCountNumber 는 영향을 받으므로 리렌더링된다.(만약 BigCountNumber가 리렌더링되지 않는다면 텍스트가 0에서 1로 바뀌지 않을 것이다.)

🤔 오해 2. props가 바뀌면 컴포넌트가 리렌더링 된다?! ❌

props와 리렌더링은 무관하다!

위 예시코드에서 <Decoration/> 컴포넌트가 하나 추가되었다.

import React from 'react';

import Decoration from './Decoration';
import BigCountNumber from './BigCountNumber';

function Counter() {
  const [count, setCount] = React.useState(0);
  
  return (
    <main>
      <BigCountNumber count={count} />
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
      
      {/* 👇 This fella is new 👇 */}
      <Decoration />
    </main>
  );
}

export default Counter;

사용자가 버튼을 클릭해서 count의 상태가 바뀌었다. 앞서 말한 것처럼 자식 컴포넌트인<BigCountNumber/>는 리렌더링 될 것이다. 그렇다면 새로운 자식 컴포넌트인<Decoration/> 은 어떨까? <Decoration/>도 자식 컴포넌트이므로 당연히 리렌더링 될 것이다. 많은 개발자들이 전달되는 props의 상태가 변화하면 리렌더링이 된다고 알고 있는데, 리렌더링은 props와 아무런 관련이 없다!💡 Counter 컴포넌트의 자식컴포넌트들이 리렌더링 되는 것은 자식 컴포넌트이기 때문에 리렌더링 되는 것이지 부모 컴포넌트의 state를 props 전달 받은 것과는 전혀 상관이 없다. 때문에 count 와 전혀 관련이 없는 <Decoration/>도 리렌더링 되는 것이다!

useMemo를 사용하는 이유
(💡 React.memo는 HOC이기 때문에 클래스형 컴포넌트, 함수형 컴포넌트 모두 사용 가능하지만, useMemo는 hook이기 때문에 오직 함수형 컴포넌트 안에서만 사용 가능하다.)


function Decoration() {
  return (
    <div className="decoration">
      ⛵️
    </div>
  );
}
export default React.memo(Decoration);

<Decoration/>을 useMemo로 감싸줌으로써, 우리는 리액트에게 <Decoration/>는 항상 Pure Component이기 때문에 리렌더링할 필요가 없음을 알린다.
count가 변화하여 <Counter/> 가 리렌더링될 때 리액트는 두 자식 컴포넌트 (
<BigCountNumber/>,<Decoration/>)의 리렌더링을 시도할 것이지만, useMemo로 감싼 <Decoration/>은 리렌더링되지 않는다.(불필요한 리렌더링을 막음으로써 성능개선 효과를 기대할 수 있다.)

정리
✅ 컴포넌트의 state가 변화했을 때 해당 state를 소유한 컴포넌트가 리렌더링 된다.(본인)
✅ 그 컴포넌트에 속한 자손 컴포넌트들이 리렌더링된다.(props전달 여부와 무관)

profile
나를 믿고 한 걸음 한 걸음 내딛기! 🍏
post-custom-banner

0개의 댓글