Container/Presentational 패턴
비즈니스 로직으로부터 뷰를 분리하여 관심사의 분리(SoC) 를 강제한다
📜 원문: patterns.dev - container/presentational pattern
📜 번역: https://patterns-dev-kr.github.io/design-patterns/container-presentational-pattern/
6개의 강아지 사진을 다운로드받아 화면에 렌더하는 앱을 만든다고 가정해 보죠.
이상적으로는 이 프로세스를 아래 두가지로 분리하여 관심사의 분리를 강제하고 싶습니다.
- Presentational Components: 데이터가 어떻게 사용자에게 보여질 지에 대해서만 다루는 컴포넌트. 예제에서는 강아지 사진의 목록을 렌더링하는 부분이다.
- Container Components: 어떤 데이터가 보여질 지에 대해 다루는 컴포넌트. 예제에서는 강아지 사진들을 다운로드한다.
강아지 사진을 다운로드하는것은 비즈니스 로직의 역할이고. 이미지를 보여주는 것은 뷰의 역할이죠.
Presentational Component
Presentational 컴포넌트는 props를 통해 데이터를 받습니다. 이 컴포넌트의 주요 기능은 받은 데이터를 화면에 표현하는것이며 그 목적을 위해 스타일시트를 포함합니다. 데이터는 건드리지 않죠.
아래 강아지 사진을 출력하는 예제를 확인해 보죠. 강아지 사진을 렌더링 할 때 단순히 각 이미지들을 API로부터 다운로드 받고 화면에 렌더링 하는데 그렇게 하지 말고.이미지들을 props를 통해 받아 화면에 그리는 함수형 컴포넌트만 만드는 것입니다.
여기서 DogImages 컴포넌트는 Presentational 컴포넌트입니다.
Presentational 컴포넌트는 UI 변경을 위한 상태 외에는 상태를 갖지 않습니다. prop을 통해 받은 데이터는 Presentational 컴포넌트에 의해 수정되지 않죠. Presentational 컴포넌트는 Container 컴포넌트로부터 데이터를 받습니다.
Container 컴포넌트
Container 컴포넌트의 주요 기능은 Presentational 컴포넌트에 데이터를 전달하는 것입니다. Container 컴포넌트 자체는 화면에 아무것도 렌더링하지 않습니다. Container 컴포넌트는 아무것도 화면에 그리지 않으니 스타일시트도 포함하지 않습니다.
아래 예제에서 강아지 사진 목록을 DogImages 컴포넌트에 전달하기 위해 Container 컴포넌트를 만들었습니다.
만들어진 Container 컴포넌트는 외부 API로부터 강아지 이미지를 다운로드하고 Presentational 컴포넌트인 DogImages 컴포넌트에게 전달하고 있습니다.
위의 두 컴포넌트를 조합하면 비즈니스 로직과 뷰를 분리하고 있습니다.
Hooks
대개 Container/Presentational 패턴은 React Hooks로 대체 가능합니다.
React 에 Hooks가 추가되면서 Container 컴포넌트 없이도 stateless 컴포넌트를 쉽게 만들 수 있게 되었습니다.
DogImagesContainer 컴포넌트에 있는 데이터 로드 코드를 아래와 같이 커스텀 훅으로 만들 수 있죠.
export default function useDogImages() {
const [dogs, setDogs] = useState([])
useEffect(() => {
fetch('https://dog.ceo/api/breed/labrador/images/random/6')
.then(res => res.json())
.then(({ message }) => setDogs(message))
}, [])
return dogs
}
위의 훅을 사용하면 데이터를 받아오기 위해 DogImagesContainer 컴포넌트를 사용할 필요가 없습니다.
대신 Presentational 컴포넌트인 DogImages 에서 훅을 직접 호출해 사용하면 됩니다.
<iframe src="https://codesandbox.io/embed/hoc-pattern-1-tzp7i?fontsize=14&hidenavigation=1&theme=dark"
style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"
title="hoc-pattern-1"
allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"
sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"
></iframe>
useDogImages 훅을 사용하여 Container/Presentational 패턴을 사용한 것과 같이 비즈니스 로직과 뷰를 분리했습니다.
DogImages 는 단순히 훅에서 반환된 값을 수정없이 사용하면 됩니다.
훅은 이 장에서 소개한 패턴처럼 비즈니스 로직과 뷰를 쉽게 분리할 수 있게 해주고. 불필요한 Container 래핑을 줄일 수 있게 해 줍니다.
장점
Container/Presentational 패턴은 여러 장점들을 가지고 있습니다.
해당 패턴을 활용하면 자연스럽게 관심사의 분리를 구현하게 됩니다.
Presentational 컴포넌트는 UI를 담당하는 순수함수로 작성하게 되는 반면 Container 컴포넌트는 상태와 기타 데이터를 책임지게 됩니다.
Presentational 컴포넌트는 데이터 변경 없이 화면에 출력할 수 있으므로 앱의 여러 곳에서 다양한 목적으로 재사용할 수 있죠.
Presentational 컴포넌트는 앱의 비즈니스 로직을 수정하지 않으므로 코드베이스에 대한 이해가 깊지 않은 개발자더라도 쉽게 수정이 가능합니다. 공통으로 쓰이는 Presentational 컴포넌트가 디자인의 요구사항에 따라 수정하면 앱 전체에서 반영됩니다.
Presentational 컴포넌트는 테스트하기도 쉽습니다. 일반적으로 순수함수로 구현되므로 전체 목 데이터 스토어를 만들 필요 없이 요구하는 데이터만 인자로 넘겨주면 됩니다.
단점
Container/Presentational 패턴은 비즈니스 로직과 렌더링 로직을 쉽게 분리할 수 있지만 훅을 활용하면 클래스형 컴포넌트
를 사용하지 않고도, 또 이 패턴을 따르지 않아도 같은 효과를 볼 수 있다. 참고로 지금은 상태를 가진 컴포넌트도 함수형으로 만들 수 있습니다.
훅을 사용하더라도 이 패턴을 사용할 수는 있지만 너무 작은 규모의 앱에서는 오버엔지니어링 일 수 있습니다.
참조
'프로그래밍(Basic) > 디자인 패턴(JS)' 카테고리의 다른 글
[바미] Module 패턴 (0) | 2022.09.21 |
---|---|
[바미] Observer 패턴 (1) | 2022.09.19 |
[바미] Prototype 패턴 (0) | 2022.09.14 |
[바미] Provider 패턴 (0) | 2022.09.13 |
[바미] Proxy 패턴 (0) | 2022.09.12 |