Presentational and Container Components (번역)

Kim Seungha
8 min readJun 17, 2018

--

(원문: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)

저는 React 어플리케이션을 작성할 때 아주 유용하게 사용될 수 있는 패턴을 고안했습니다. React를 장기간 사용하신 분들은, 이미 이 패턴을 발견하셨을 것입니다. 이 글에서도 잘 설명하고 있지만, 몇 가지 포인트를 덧붙이고자 합니다.

재사용성과 유지보수성을 높이기 위해서 컴포넌트를 두 가지 종류로 나누어 생각할 수 있습니다. 저는 ContainerPresentational 컴포넌트*로 부르고 있지만, Fat and Skinny, Smart and Dumb, Stateful and Pure, Screens and Components로 부르는 경우도 있습니다. 이 분류법들이 정확히 같은 것은 아닙니다만, 핵심적인 사고방식은 비슷합니다.

제가 고안한 presentational 컴포넌트는 아래와 같은 성질을 가지고 있습니다:

  • 어떻게 보여질지를 책임집니다.
  • 내부에 presentational 컴포넌트와 container 컴포넌트** 모두를 가질 수 있고, 대개 DOM 마크업과 자체 스타일을 가지고 있습니다.
  • this.props.children을 통해 다른 컴포넌트를 포함하게끔 만들 수 있습니다.
  • 어플리케이션의 나머지 부분에 대해 아무런 의존성을 가지지 않습니다. (예를 들면 Flux 액션이나 스토어 등)
  • 데이터를 불러오거나 변경하는 작업은 여기에 작성되지 않습니다.
  • 데이터 및 데이터와 관련된 콜백은 props를 통해서 받기만 합니다.
  • (데이터가 아닌) UI 상태를 관리하기 위해 state를 갖는 경우가 있습니다.
  • state, 라이프사이클 훅, 성능 최적화가 필요없는 경우라면 함수형 컴포넌트로 작성됩니다.
  • 예시: Page, Sidebar, Story, UserInfo, List.

제가 고안한 container 컴포넌트는 아래와 같은 성질을 가지고 있습니다:

  • 어떻게 동작해야 할지를 책임집니다.
  • 내부에 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 컴포넌트를 추출해야 할지에 대한 감을 잡으실 수 있을 겁니다. 마치 언제 함수를 추출해야 할지를 알게 되는 것과 같이 말이죠. Egghead에 있는 저의 무료 Redux 시리즈도 도움이 될 것입니다!

다른 분류법

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여야 하는지 결정하기 어렵게 느껴진다면, 아마도 그 결정을 하기 이른 때일 것입니다. 너무 애쓰지 마세요!

예제

Michael Chan이 작성한 이 gist가 명확히 보여주고 있습니다.

더 읽을거리

각주

* 이전 버전의 글에서 저는 “smart”와 “dumb”라는 용어를 썼습니다만, 이는 presentational 컴포넌트를 표현하기에 너무 가혹한 용어일 뿐더러, 무엇보다도 각각의 목적을 명확히 설명해주지 못합니다. 저는 새로운 용어가 훨씬 더 좋다고 느끼고 있으며, 여러분도 그러길 바랍니다!

** 이전 버전의 글에서 저는 presentational 컴포넌트가 오직 presentational 컴포넌트만을 포함해야 한다고 주장했습니다. 하지만 더 이상 그렇게 생각하지 않습니다. 어떤 컴포넌트가 presentational인지 container인지는 구현 세부사항입니다. Presentational 컴포넌트는 그것을 사용하는 쪽에서 어떠한 수정도 없이 container 컴포넌트로 교체될 수 있어야 합니다. 따라서, presentational 컴포넌트든 container 컴포넌트든 상관없이 다른 presentational 컴포넌트 혹은 container 컴포넌트를 포함해도 상관 없다고 생각하는 것이 맞습니다.

--

--