[TIL 9/13] Container-Presentational Pattern

haegnim·2023년 9월 13일
0

TIL

목록 보기
50/52

컨테이너 프레젠테이셔널 패턴 (Container-Presentational Pattern)
React와 같은 컴포넌트 기반의 프레임워크에서 UI와 로직을 분리하는 데 사용됩니다.

선결론

처음 이 패턴을 소개한 Dan Abramov는 2019년 기준으로 현재는 이 패턴을 사용하지 말라고 언급

내가 이 패턴을 유용하게 보았던 주된 이유는 이 패턴이 다른 관점의 컴포넌트로부터 
복잡하고 stateful한 로직을 분리하게 해주었기 때문이다.
임의의 분리없이 Hook은 똑같은 일을 할 수 있다.

하지만 해당 패턴이 등장하게 된 이유와 방법은 현재에도 의미가 있으니 역사적인 자료로서 공부할 것.


등장 배경

리액트의 컴포넌트는 상태, DOM, 이벤트 등을 모두 관리 가능.

문제 제기

컴포넌트 간의 의존도가 높아지면 코드의 재사용 불가

컴포넌트 내에서도 추가적으로 레이어를 적절히 나눠 의존도를 낮춰주어야 할 필요가 있음

해결책 제시

로직과 view를 분리 ➡️  Presentational and Container 패턴


Container / Presentational 패턴

Container 컴포넌트

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


  1. 어떠한 동작을 할 것인가에 대해 책임진다.
  2. 절대로 DOM 마크업 구조나 스타일을 가져서는 안된다.
  3. presentational과 container 모두를 내부적으로 가질 수 있다.
  4. 주로 상태를 가지고 있다.
  5. side effects를 만들 수 있다. ex) db에 CRUD를 요청
  6. props를 자유롭게 받을 수 있다.

Presentational 컴포넌트

Presentational 컴포넌트는 props를 통해 데이터를 받고 주요 기능은 받은 데이터를 화면에 표현.

스타일시트를 포함하는 반면, UI 변경을 위한 상태 외에는 상태를 갖지 않는다. prop을 통해 받은 데이터는 Presentational 컴포넌트에 의해 수정되지 않는다.


  1. html, css, presentational component만 사용 가능하다.
  2. app에 대해 완전히 몰라야한다.
  3. presentational과 container 모두를 내부적으로 가질 수 있다.
  4. 작은 레고 블럭처럼 가능한 작게 만들어야 한다.
  5. 상태를 가질 수 있지만 UI에 관련된 상태만 가질 수 있다.
  6. 필요 시 visual을 바꾸는 props를 받을 수 있어야 한다.
  7. 가끔 완전히 다른 스타일을 불러오는 props를 받기도 한다.

예시

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} />;
  }
}
import React from "react";

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

지금은 맞지 않은 이유 - Hooks의 등장

Hooks

대개 Container/Presentational 패턴은 React Hooks로 대체 가능. React 에 Hooks가 추가되면서 Container 컴포넌트 없이도 stateless 컴포넌트를 쉽게 만들 수 있게 되었다.

DogImagesContainer 컴포넌트에 있는 데이터 로드 코드를 아래와 같이 커스텀 훅으로 만들 수 있다.

훅은 비즈니스 로직과 뷰를 쉽게 분리할 수 있게 해주고. 불필요한 Container 래핑을 줄일 수 있게 해 준다.

예시 1 : 데이터 패치

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
}
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" />);
}

예시 2 : 상태 관리

import React, { useState, useEffect } from 'react';

function useLightbox() {
  const [isOpen, setIsOpen] = useState(false);
  const [imageSrc, setImageSrc] = useState('');

  const openLightbox = (src) => {
    setImageSrc(src);
    setIsOpen(true);
  };

  const closeLightbox = () => {
    setImageSrc('');
    setIsOpen(false);
  };

  return {
    isOpen,
    imageSrc,
    openLightbox,
    closeLightbox,
  };
}
function Lightbox() {
  const { isOpen, imageSrc, openLightbox, closeLightbox } = useLightbox();

  return (
    <div>
      <h1>이미지 라이트 박스</h1>
      <button onClick={() => openLightbox('image.jpg')}>이미지 열기</button>
      {isOpen && (
        <div className="lightbox">
          <img src={imageSrc} alt="이미지" />
          <button onClick={closeLightbox}>닫기</button>
        </div>
      )}
    </div>
  );
}

장점과 단점

장점(목적)

  1. 재사용성 증가
    • 프레젠테이션 컴포넌트는 오직 화면출력 역할만 수행함으로 다양한 목적으로 재사용할 수 있음
  2. 관심사의 분리(뷰와 로직 분리)
    • 일반적으로 순수함수로 구현되어 요구하는 데이터만 인자로 넘겨주는 방식으로 테스트하기 쉬움
    • 구조에 대한 이해가 쉬워짐
      • 비즈니스 로직이 포함되지 않아 코드베이스에 대한 이해가 깊지 않아도 수정 가능

단점

  1. hook으로 대체 가능
  2. 작은 규모에서는 오버엔지니어링

정리

과거 존재했던 이유

  1. 로직과 순수한 view를 나눠 view를 재사용이 가능한 형태로 강제했다.
  2. 뷰과 로직을 분리해 복잡도를 낮추었다.
    마크업 작업이 필요하면 presentational을 보고 로직 수정이 필요하면 container를 보는 등 유지보수하는 이점

현재 적절하지 않은 이유

  1. bottom-up 방식 개발, 아토믹 패턴등으로 컴포넌트를 작은 단위로 나눠 재사용 가능한 형태로 개발
  2. 리액트 Hook이 등장하며 로직과 표현의 분리가 가능해짐

생각해 볼 가치

  • 컴포넌트의 단일 책임 원칙(Single Responsibility Principle) 준수
  • 로직과 뷰의 분리는 여전히 유용
  • 컴포넌트 재사용을 고려한 설계
  • 컴포넌트는 DOM을 생성할 필요가 없다는 걸 명심.

참고자료

Container/Presentational 패턴

Presentational and Container Components

presentational and container 패턴이란 무엇인가

0개의 댓글