불필요한 렌더링을 막아라: shouldComponentUpdate

772025년 10월 31일4

리액트의 생명주기 모델은 점점 정교해졌지만, 성능 전문가인 데이빗의 눈에는 여전히 풀리지 않은 의문이 하나 남아있었다. 그는 팀이 만든 뉴스피드 프로토타입의 성능을 프로파일링 도구로 분석하다가 한 가지 비효율적인 패턴을 발견했다.

“조던, 여기 좀 보시죠.”

데이빗은 자신의 스크린을 가리켰다. 화면에는 뉴스피드의 부모 컴포넌트인 <NewsfeedContainer>setState를 호출할 때마다, 그 아래에 있는 수십 개의 <Story> 자식 컴포넌트들의 render 함수가 예외 없이 모두 호출되는 로그가 찍히고 있었다.

“문제는, 이 중에서 실제로 props가 변경된 <Story> 컴포넌트는 단 하나뿐이라는 겁니다. 나머지 49개의 컴포넌트는 이전과 똑같은 props를 받았는데도, 불필요하게 render를 다시 실행하고, 버추얼 DOM을 또 만들고, 비교하는 작업을 반복하고 있어요.”

그의 지적은 날카로웠다. 버추얼 DOM의 비교(Diffing)가 아무리 빠르다고 해도, 애초에 비교할 필요조차 없는 상황에서 render 함수를 실행하는 것 자체가 명백한 자원의 낭비였다. 특히 <Story> 컴포넌트의 render가 복잡한 계산을 수행한다면, 이 낭비는 무시할 수 없는 성능 저하로 이어질 터였다.

“리액트의 기본 원칙은 ‘일단 다시 그려보고, 달라진 게 없으면 무시한다’는 것 아니었습니까?” 데이빗이 물었다. “하지만 때로는 우리가 ‘달라질 게 없다’는 사실을 미리 알고 있을 때도 있습니다. 그럴 때 이 비싼 재계산 과정을 건너뛸 방법은 없나요?”

그것은 리액트의 단순함이라는 철학에 대한 정면 도전이자, 실제 대규모 애플리케이션을 위한 필수적인 요구사항이었다.

조던 워크는 이 질문을 기다렸다는 듯, 생명주기 목록에서 아직 논의되지 않았던 특별한 메서드 하나를 꺼내 들었다.

shouldComponentUpdate(nextProps, nextState)

이름부터 다른 생명주기 메서드와는 달랐다. ‘Did’나 ‘Will’이 아닌, ‘Should’. 그것은 리액트가 컴포넌트에게 던지는 질문이었다.

“내가 너를 다시 렌더링해야 할까? (Should I update you?)”

  • 이 메서드는 setState가 호출되거나 새로운 props를 받은 후, render가 호출되기 ‘직전’에 실행된다.
  • 이 메서드는 반드시 불리언(boolean) 값, 즉 true 또는 false를 반환해야 한다.
  • 만약 true를 반환하면(이것이 기본 동작이다), 리액트는 평소처럼 render를 호출하고 업데이트를 진행한다.
  • 만약 false를 반환하면, 리액트는 업데이트 과정 전체를 중단한다. render 함수는 아예 호출되지 않고, 버추얼 DOM 비교도 일어나지 않는다.

조던은 이 강력한 ‘밸브’를 사용하는 예시를 보여주었다.

var Story = React.createClass({
  // 이 컴포넌트를 다시 렌더링해야 할지 말지 직접 결정한다.
  shouldComponentUpdate: function(nextProps, nextState) {
    // 만약 다음 props로 받을 story의 ID가 현재 ID와 다르다면,
    // 그때만 렌더링을 허락한다. (true 반환)
    // ID가 같다면, 다른 내용도 바뀌지 않았을 것이라 가정하고
    // 렌더링을 막는다. (false 반환)
    return this.props.story.id !== nextProps.story.id;
  },

  render: function() {
    console.log(this.props.story.id + ' 컴포넌트가 렌더링됩니다.');
    // ... 실제 스토리 렌더링 로직 ...
  }
});

데이빗은 이 코드를 보고 감탄했다.
“이건… 개발자에게 최적화를 위한 ‘탈출구’를 열어주는 거군요.”

그렇다. shouldComponentUpdate는 리액트의 자동화된 렌더링 흐름에 개발자가 직접 개입할 수 있는 유일한 통로였다. 평소에는 리액트의 편리함에 몸을 맡기다가, 성능이 극도로 중요한 특정 컴포넌트에서만 이 밸브를 사용하여 불필요한 작업을 원천적으로 차단할 수 있었다.

이 메서드의 도입으로, 리액트는 ‘단순함’과 ‘성능’이라는 두 마리 토끼를 모두 잡을 수 있는 유연성을 갖추게 되었다. 개발자는 이제 애플리케이션의 95%는 리액트에 맡겨 편하게 개발하고, 나머지 5%의 성능 병목 구간은 shouldComponentUpdate라는 날카로운 메스로 직접 도려낼 수 있었다. 리액트는 비로소 거대한 트래픽을 감당해야 하는 페이스북의 심장부에 적용될 수 있는, 실용적인 자격을 갖추게 된 것이다.