리액트(React) 이해 기초 - Component vs PureComponent vs Functional Component
June 13, 2017
리액트(React)에서 컴포넌트를 만드는 방법에는 크게 클래스 기반(React.Component
, React.PureComponent
를 확장(extends)해서 사용)과 함수 기반(Function Component
)으로 나눌 수 있다. (React.createClass
는 deprecated 되었고, 16 버전에서는 없어지기 때문에 제외 한다.) 이 글에서는 이 세 가지 방법의 다른 점을 비교 해보고(문법(syntax)은 제외), 퍼포먼스에 대한 얘기를 조금 해보도록 하겠다.
먼저, 클래스 기반 컴포넌트 (React.Component
, React.PureComponent
)에 대해 알아보자. 사실, 두 개는 shouldComponentUpdate 라이프 사이클 메소드를 다루는 방식을 제외하곤 같다. 즉, PureComponent
는 shouldComponentUpdate
라이프 사이클 메소드가 이미 적용 된 버전의 React.Component 클래스라고 보면 된다.
React.Component
를 확장(extends)해서 컴포넌트를 만들 때, shouldComponentUpdate
메서드를 별도로 선언하지 않았다면, 컴포넌트는 props
, state
값이 변경되면 항상 리렌더링(re-render) 하도록 되어 있다.
하지만, React.PureComponent
를 확장해서 컴포넌트를 만들면, shouldComponentUpdate
메서드를 선언하지 않았다고 하더라도, PureComponent
내부에서 props
와 state
를 shallow level 안에서 비교 하여, 변경된 값이 있을 시에만 리렌더링 하도록 되어 있다.
이를 제외하곤 React.Component
와 React.PureComponent
의 다른 점은 없다. 그렇다면, 함수 컴포넌트가 클래스 기반의 컴포넌트와 다른점은 무엇일까? 함수 컴포넌트는 클래스 기반의 컴포넌트와 달리, state
, 라이프 사이클 메소드(componetDidMount
, shouldComponentUpdate
등등..)와 ref
콜백을 사용 할 수 없다는데 있다(context는 사용 할 수 있다). (React 16.8 버전 이후에는 hooks
API가 추가 되어 함수 컴포넌트에서도 state
관리를 할 수 있다.) 언뜻 보면, 함수 컴포넌트가 state도 없고, 라이프 사이클도 신경쓰지 않기 때문에, 클래스 기반의 컴포넌트 보다 퍼포먼스가 뛰어날 것이라고 예상 하지만 실제로는 그렇지 않다.
아래의 글에서도 확인 할 수 있듯이, 함수 컴포넌트가 클래스 컴포넌트의 퍼포먼스보다 우위에 있다고 하기엔 어렵다. 오히려, React.PureComponent
가 React.Component
와 함수 컴포넌트 보다 더 빠른 걸 알 수 있다. React.PureComponent
는 shouldComponentUpdate
를 통해 리렌더링을 최소화 하는 로직이 들어가 있으니 그렇다 치더라도 함수 컴포넌트와 클래스 컴포넌트의 속도 차이가 없는 것은 의외라고 생각 할 수 있다.
사실, 그 이유는 의외로 간단한데, 함수 컴포넌트도 결국엔 클래스 기반 컴포넌트로 래핑(wrapping)되기 때문이다. 아래 Dan의 트윗을 먼저 확인해보자. 향 후에 16 버전 또는 더 미래에는 함수 컴포넌트의 퍼포먼스 우위가 있을 수 있지만 현재로는 아니다.
I'm not sure if we explained this elsewhere but they are not faster because they *are* classes internally pic.twitter.com/oGZOYFvkG9
— дэн (@dan_abramov) July 19, 2016
There is no “optimized” support for them yet because stateless component is wrapped in a class internally. It's same code path.
— дэн (@dan_abramov) July 19, 2016
그렇다면, 항상 React.PureComponent를 사용 하는 게 좋다고 할 수 있을까? 대답은 당연히 아니다. 위에서도 언급 했지만, PureComponent는 shallow level로만 데이터를 비교하기 때문에, nested object 등의 변경된 데이터는 감지하지 못하기 때문에 React.Component의 shouldComponentUpdate 직접 다뤄야 한다. 또한, 퍼포먼스 적으로도 모든 컴포넌트에 PureComponent를 사용 하는 것은 오히려 앱을 더 느리게 할 수도 있다.
PSA: React.PureComponent can make your app slower if you use it everywhere.
— дэн (@dan_abramov) January 15, 2017
Think about it. If component’s props are shallowly unequal more often than not, it re-renders anyway, but it also had to run the checks.
— дэн (@dan_abramov) January 15, 2017
클래스 컴포넌트(state, lifecycle, ref)가 필요한 상황이 아니라면 항상 컴포넌트는 함수로 만드는 게 좋다.
그렇다면, 함수 컴포넌트를 그대로 유지하면서 리렌더링을 최소화 할 수 있는 방법은 없을까? 리액트 유틸리티(higher order components) 라이브러리인 recompose 등을 사용 하면 함수 컴포넌트를 그대로 유지하면서 PureComponent(pure)의 효과를 누리거나 그보다 더 나은 퍼포먼스 상에 이점(onlyUpdateForKeys, onlyUpdateForPropTypes)을 가질 수 있는 함수들이 있기 때문에 이런 라이브러리를 적극 활용 하면 함수 컴포넌트를 그대로 유지하면서 리렌더링을 최소화 할 수 있다.
pure HOC(Higher Order Component)의 경우 React.PureComponent와 로직상 같고, onlyUpdateForKeys, onlyUpdateForPropTypes HOC는 특정 props 값이 변경 되었을 때만 리렌더링을 하도록 하기 때문에, pure, React.PureComponent 보다 퍼포먼스 상에 우위를 가져 갈 수 있다.
리액트의 세 가지(React.Component, React.PureComponent, Functional Component) 컴포넌트의 다른 점과 리렌더링을 다루는 방법에 대해 얘기했다. 리액트를 처음 접하면 쉽게 오해 할 수 있는 부분 인데, 이 글을 통해 조금 더 리액트를 이해 할 수 있었으면 좋겠다.