[Design Patterns] Container/Presentational 패턴

·2024년 1월 31일
0

patterns

목록 보기
5/11
post-thumbnail

어쩌면 유일하게(?) 알고 있는 패턴...!
부트캠프에서 Container/Presentational 패턴을 기본적으로 가르쳐서 부트캠프 플젝은 다 이 패턴으로 구현되어 있다.
이 블로그를 작성하며 공부하다 보니 Hooks 형태로 react를 배웠는데 왜 굳이 이 패턴을 기본으로 가르쳐준건지 의아함이 생겼다.

Container/Presentational 패턴

React에서 관심사의 분리(SoC)를 강제하는 방법은 Container/Presentational Pattern을 이용하는 방법이 있다.
이를 통해 비즈니스 로직에서 뷰를 분리해낼 수 있다.

6개의 강아지 사진을 다운로드 받아 화면에 렌더하는 앱을 만든다고 가정해보자.
이상적으로는 이 프로세스를 두 가지로 분리하여 관심사의 분리를 강제하고 싶다.

  • Presentational Components : 데이터가 어떻게 사용자에게 보여질지에 대해서만 다루는 컴포넌트로, 예제에서는 강아지 사진의 목록을 렌더링하는 부분이다.
  • Container Components : 어떤 데이터가 보여질지에 대해 다루는 컴포넌트로, 예제에서는 강아지 사진들을 다운로드한다.

강아지 사진을 다운로드 하는 것이 비즈니스 로직의 역할이고, 이미지를 보여주는 것은 뷰의 역할이다.

Presentational Component

Presentational 컴포넌트는 props를 통해 데이터를 받는다. 이 컴포넌트의 주요 기능은 받은 데이터를 화면에 표현하는 것이며 그 목적을 위해 스타일 시트를 포함한다.

import React from "react";

export default function DogImages({dogs}) {
  return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);
}

여기서 DogImages 컴포넌트는 Presentational 컴포넌트이다. Presentational 컴포넌트는 UI 변경을 위한 상태 외에는 상태를 갖지 않는다.
prop을 통해 받은 데이터는 Presentational 컴포넌트에 의해 수정되지 않는다. Presentational 컴포넌트는 Container 컴포넌트로부터 데이터를 받는다.

Container Component

Container 컴포넌트의 주요 기능은 Presentational 컴포넌트에 데이터를 전달하는 것이다. Container 컴포넌트 자체는 화면에 아무것도 렌더링하지 않는다. Container 컴포넌트는 아무것도 화면에 그리지 않기 때문에 스타일 시트도 포함하지 않는다.

import React from "react";
import DogImages from "./DogImages";

export default class DogImagesContainer extends React.Component {
  constructor() {
    super();
    this.state = {
      dogs: []
    };
  }

  componentDidMount() {
    fetch("https://dog.ceo/api/breed/labrador/images/random/6")
      .then(res => res.json())
      .then(({ message }) => this.setState({ dogs: message }));
  }

  render() {
    return <DogImages dogs={this.state.dogs} />;
  }
}

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에서 훅을 직접 호출해 사용하면 된다.

import React from "react";
import useDogImages from "./useDogImages";

export default function DogImages() {
  const dogs = useDogImages();

  return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);
}

Hooks는 Container/Presentational 패턴처럼 비즈니스 로직과 뷰를 쉽게 분리할 수 있게 해주고, 불필요한 Container 랩핑을 줄일 수 있게 해준다.

장점, 단점

장점

  • 관심사의 분리를 자연스럽게 구현하게 된다. Presentational 컴포넌트는 UI를 담당하는 순수 함수로 작성하게 되는 반면, Container 컴포넌트는 상태와 기타 데이터를 책임지게 된다.
  • Presentational 컴포넌트는 데이터 변경 없이 화면에 출력할 수 있으므로 앱의 여러 곳에서 다양한 목적으로 재사용할 수 있다.
  • Presentational 컴포넌트는 앱의 비즈니스 로직을 수정하지 않으므로 코드베이스에 대한 이해가 깊지 않은 개발자이더라도 쉽게 수정이 가능하다. 공통으로 쓰이는 Presentational 컴포넌트를 디자인의 요구사항에 따라 수정하면 앱 전체에 반영된다.
  • Presentational 컴포넌트는 테스트하기도 쉽다. 일반적으로 순수함수로 구현되므로 전체 mock 데이터 스토어를 만들 필요 없이 요구하는 데이터만 인자로 넘겨주면 된다.

단점

  • 비즈니스 로직과 렌더링 로직을 쉽게 분리할 수 있지만, 훅을 활용하면 클래스형 컴포넌트를 사용하지 않고도 또한 이 패턴을 따르지 않고도 같은 효과를 볼 수 있다.
    훅을 사용하더라도 이 패턴을 사용할 수는 있지만, 너무 작은 규모의 앱에서는 오버엔지니어링일 수 있다.

< 출처 : https://patterns-dev-kr.github.io/design-patterns/container-presentational-pattern/ >

profile
개발을 개발새발 열심히➰🐶

0개의 댓글