React 컴포넌트 성능 최적화 - shouldComponentUpdate 실전 적용

문제 상황

사내 대시보드 프로젝트에서 차트 컴포넌트가 포함된 화면의 성능 이슈가 있었다. 상위 컴포넌트에서 타이머로 시간을 업데이트할 때마다 모든 차트가 리렌더링되면서 브라우저가 버벅였다.

React DevTools Profiler로 확인해보니 props가 변경되지 않았는데도 차트 컴포넌트들이 매번 렌더링되고 있었다.

해결 방법

1. shouldComponentUpdate 구현

class ChartCard extends React.Component {
  shouldComponentUpdate(nextProps) {
    return this.props.data !== nextProps.data ||
           this.props.title !== nextProps.title;
  }

  render() {
    const { data, title } = this.props;
    return (
      <div className="chart-card">
        <h3>{title}</h3>
        <LineChart data={data} />
      </div>
    );
  }
}

2. PureComponent 활용

props가 단순한 경우 PureComponent로 교체하는 것이 더 간결했다.

class StatCard extends React.PureComponent {
  render() {
    const { value, label } = this.props;
    return (
      <div className="stat-card">
        <span className="value">{value}</span>
        <span className="label">{label}</span>
      </div>
    );
  }
}

PureComponent는 shallow comparison을 자동으로 해주기 때문에 별도로 shouldComponentUpdate를 작성할 필요가 없었다.

주의사항

객체나 배열을 props로 전달할 때는 참조가 바뀌지 않도록 주의해야 한다. 상위 컴포넌트에서 매번 새 객체를 생성하면 최적화가 무용지물이 된다.

// Bad - 매번 새 객체 생성
render() {
  return <ChartCard data={{ values: this.state.values }} />;
}

// Good - 참조 유지
render() {
  return <ChartCard data={this.state.chartData} />;
}

결과

Chrome DevTools Performance 탭으로 측정한 결과, 1초당 렌더링 횟수가 15회에서 4회로 줄었다. 프레임 드롭도 거의 사라졌다.

최적화는 측정 가능한 병목이 있을 때만 적용하는 것이 좋다. 모든 컴포넌트에 무작정 적용하면 오히려 코드만 복잡해진다.