Presentational and Container Components
재사용성과 유지보수성을 높이기 위해서 컴포넌트를
Container 와 Presentational 컴포넌트라는 두 가지 종류로 나눌 수 있습니다.
Presentational Component
- 어떻게 보여질지를 책임집니다.
- 내부에 presentational 컴포넌트와 container 컴포넌트 모두를 가질 수 있고, 대개 DOM 마크업과 자체 스타일을 가지고 있습니다.
- this.props.children 을 통해 다른 컴포넌트를 포함하게끔 만들 수 있습니다.
- 어플리케이션의 나머지 부분에 대해 아무런 의존성을 가지지 않습니다. (예를 들면 Flux 액션이나 스토어 등)
- 데이터를 불러오거나 변경하는 작업은 여기에 작성되지 않습니다.
- 데이터 및 데이터와 관련된 콜백은 props 를 통해서 받기만 합니다.
- (데이터가 아닌) UI 상태를 관리하기 위해 state 를 갖는 경우가 있습니다.
- state, 라이프사이클 훅, 성능 최적화가 필요없는 경우라면 함수형 컴포넌트로 작성됩니다.
- 예시: Page, Sidebar, Story, UserInfo, List.
Container Component
- 어떻게 동작해야 할지를 책임집니다.
- 내부에 presentational 컴포넌트와 container 컴포넌트 모두를 가질 수 있지만, 대개 (전체를 감싸는 div 를 제외하면) 자체적인 DOM 마크업이나 스타일을 갖고 있지 않습니다.
- 데이터 및 데이터와 관련된 동작을 다른 presentational 컴포넌트와 container 컴포넌트에게 제공합니다.
- Flux 액션을 호출하는 작업을 여기에 작성하며, 이 콜백들을 다른 presentational 컴포넌트에 넘겨줍니다.
- 주로 데이터 저장소로 활용되며 state 를 갖고 있는 경우가 많습니다.
- 직접 작성되기 보다는 higher order components 로부터 생성되는 경우가 많습니다.
(React Redux 의 connect(), Relay 의 createContainer(),Flux Utils 의 Container.create()) - 예시: UserPage, FollowersSidebar, StoryContainer, FollowedUserList.
이러한 접근 방식의 이점
-
관심사의 분리를 더 잘할 수 있습니다. 이 방식으로 컴포넌트를 작성하면 여러분의 앱과 UI 를 이해하기 쉽게 만들 수 있습니다.
-
재사용성을 높일 수 있습니다. 완전히 다른 곳으로부터 온 여러 상태라 할지라도,
이를 표현하기 위해 같은 presentational 컴포넌트를 사용할 수 있습니다.
이 때 각 상태를 나타내는 container 컴포넌트를 만들어 이를 또 재사용할 수 있습니다.
(중립적인 ) -
Presentational 컴포넌트는 말하자면 앱의 “팔레트” 역할을 합니다.
이 컴포넌트들을 데모 페이지에 넣어두고, 디자이너에게 디자인 세부사항을 수정하는 작업을 맡길 수 있습니다.
디자이너는 앱의 로직을 건드릴 필요가 없습니다. 또한 데모 페이지에 대해 스크린샷 회귀 테스트를 수행할 수도 있습니다. -
이 패턴을 제대로 적용하려면 “레이아웃 컴포넌트”를 추출해야 합니다.
레이아웃 컴포넌트를 추출하면, 똑같은 레이아웃 마크업을 여러 container 컴포넌트에 작성하는 작업을 피할 수 있습니다.
컴포넌트가 DOM 을 출력하지 않아도 괜찮습니다.
컴포넌트는 UI 와 관련된 여러 관심사들을 나누는 경계선의 역할을 합니다.
컴포넌트를 이용해 관심사를 나눈 뒤에, 이 컴포넌트들을 합성할 수 있습니다.
언제 container 컴포넌트를 작성해야 하나요?
앱을 제작할 때, 먼저 presentational 컴포넌트부터 작성해 보세요.
결국 아주 많은 prop 들을 중간 계층의 컴포넌트에 전달해야 한다는 사실을 깨닫게 될 것입니다.
그 중 어떤 컴포넌트가 props 를 그저 아래로 내려보내주고 있어서,
자식 컴포넌트가 더 많은 데이터를 필요로 할 때 중간 계층에서 일일이 이어주어야 한다면,
그 때가 바로 container 컴포넌트를 적용하기 좋은 시점입니다.
이 방법을 통해, 별로 상관도 없는 중간 계층의 컴포넌트에 부담을 지우지 않고도 맨 아래에 있는 컴포넌트에 데이터를 내려줄 수 있습니다.
이러한 리팩토링 절차는 계속 진행될 것이므로, 처음부터 모든 부분을 정확하게 작성하려고 하지 마세요.
이 패턴을 적용하다 보면, 언제 container 컴포넌트를 추출해야 할지에 대한 감을 잡으실 수 있을 겁니다.
다른 분류법
Presentational 컴포넌트와 container 컴포넌트 간의 차이점이
기술적인 부분에서 오는 것이 아니라는 점을 이해하는 것이 중요합니다.
그보다는 어떤 목적을 가지는지에 따라 분류해야 합니다.
반면에, 비슷하면서도 다른 몇 가지 기술적 분류법들이 있습니다.
-
상태(Stateful)와 무상태(Stateless).
React 의 setState()를 쓰는 컴포넌트도 있고 그렇지 않은 컴포넌트도 있습니다.
대개 container 컴포넌트는 상태를 갖고 presentational 컴포넌트는 상태를 갖지 않는 경향이 있지만,
엄격하게 지켜야 하는 규칙은 아닙니다. Presentational 컴포넌트가 상태를 가질 수도 있고,
container 컴포넌트가 상태를 갖지 않을 수도 있습니다. -
클래스와 함수.
React 0.14 버전부터, 컴포넌트는 클래스로 선언될 수도, 함수로 선언될 수도 있습니다.
함수형 컴포넌트는 정의하기는 쉽지만 클래스 컴포넌트에서만 쓸 수 있는 몇몇 기능이 빠져 있습니다.
이런 제약 중 일부는 미래에 없어질 예정이지만 현재로서는 어쩔 수 없습니다.
함수형 컴포넌트는 이해하기 쉽기 때문에, state, 라이프사이클 훅,
또는 성능 최적화가 필요하지 않은 경우 함수형 컴포넌트를 사용하는 것을 권장합니다. -
순수(pure) 혹은 비순수(impure).
사람들은 같은 props 와 state 가 주어졌을 때 같은 출력이 나오는 컴포넌트를 순수하다고 말합니다.
순수한 컴포넌트는 클래스로 정의될 수도, 함수로 정의될 수도 있으며, 상태를 가질수도, 가지지 않을 수도 있습니다.
순수한 컴포넌트의 또다른 중요한 측면은 props 나 state 의 깊은 변경사항에 의존하지 말아야 한다는 것입니다.
그에 따라 shouldComponentUpdate() 훅 내부에서 얕은 비교만을 통해 렌더링 성능을 최적화할 수 있습니다.
어떤 컴포넌트가 presentational 컴포넌트인지 container 컴포넌트인지를 명확히 결정할 수 없는 경우도 있습니다.
저의 경험에 비추어보면, presentational 컴포넌트는 무상태(stateless) 순수(pure) 함수인 경향이 있고,
컨테이너는 상태를 갖는(stateful) 순수(pure) 클래스인 경향이 있습니다.
하지만, 경험상 그렇다는 것이지 반드시 그래야 한다는 것은 아니며, 특정 상황에서는 정확히 반대인 경우도 말이 될 수 있습니다.
Presentational 컴포넌트와 container 컴포넌트 간의 구분을 너무 명확히 하려고 하지 마세요.
그게 별로 중요하지 않을 때도 있고, 또 선을 명확히 긋는 것도 어렵습니다.
어떤 컴포넌트가 presentational 이어야 하는지 아니면 container 여야 하는지 결정하기 어렵게 느껴진다면,
아마도 그 결정을 하기 이른 때일 것입니다. 너무 애쓰지 마세요!