IT,프로그래밍/React.js

[React 공부노트 #4] 컨테이너 프리젠터 (Container-Presenter) 디자인 패턴

Container-Presenter pattern

only container

기존의 개발방법은 클래스컴포넌트와 스테이트를 만들고, api서버에서 필요한 데이터를 가져오는데,모두 한곳에서 구현한다. 이것은 비교적 작은 프로젝트에서 사용할때 주로 쓰인다.

이유는, 비지니스로직과 뷰가 한곳에 존재하게 되면 코드가 여러가지 기능이 섞여있기에 프로젝트가 커지게 되면 점점 유지보수하기가 힘들어진다.

그렇기에 유지보수를 위하여 뷰와 비지니스로직을 분리해서 따로 관리를할 필요성이 있는데,

그래서 사용하는것이 컨테이너 프리젠터(container-presenter) 디자인 패턴이다.

개인적으로 비지니스 로직과 뷰를 분리한다는 점에서 백앤드에서 자주쓰이는 MVC패턴과 유사함을 느꼈다.

컨테이너 프리젠터(container-presenter) 패턴

  • 컨테이너는 data를 가지고 state를 가지고, api를 불러온다. 그리고 모든 로직을 처리한다
  • 프리젠터는 데이터를 보여줌 하지만 state를 가지고 있지도 않고 단순한 함수형 컴포넌트다
  • 프리젠터는 스타일, 컨테이더는 데이터의 개념을 가지고 있다.

index.js는 모든 페이지에서 만들어져있어서 default로 export시켜준다.

즉, 1개의 페이지의 구성은 index, container, presenter 로 이루어져 있다.

container

container는 상태(state)를 가지고 있다.

export default class extends React.Component {
  state = {
    nowPlaying: null,
    upcoming: null,
    popular: null,
    error: null,
    loading: true,
  };

  render() {
    const { nowPlaying, upcoming, popular, error, loading } = this.state;
    return (
      <HomePesenter
        nowPlaying={nowPlaying}
        upcoming={upcoming}
        popular={popular}
        error={error}
        loading={loading}
      />
    );
  }
}

contianer는 위의 소스와 같이 가질수 있는 state를 미리 정의를 해둔다.
여기에 추후에 필요한 비지니스 로직을 추가할것이다.

예를 들어서 에러 처리나 api 통신과 로직들은 container에서 처리하도록 한다.

그리고 난다음에 render()에서 보이는것과 같이 presenter에 container에서 정의한 데이터와 function들을 props로 전달하여준다.

이와 같은 방법을 통해 container는 온전히 비지니스 로직만 생각하면되고, presenter는 온천히 보여지는 view만 생각하면 되기에,

관심사 분리가 이루어져서 유지보수의 용이성을 지니게된다.

Presenter

Container에서 로직을 구현한다고 한다면, Presenter는 로직의 결과를 보여주는 파트이다. 즉, 도면을 그리는것과 같다.

 

그렇기에 index.js에서 container를 import하고, container는 presenter를 import해서 사용한다.

 

위의 container의 코드를 보게되면, DetailPesenter로 container의 render에서 return을 해주는데,

여기에 presenter가 사용할수있게 loading, error와 같은 props를 전달하여준다.

 

이때, 만일 state값이 변경이 된다면, 그럴때 마다 새롭게 reder를 해주게된다.

 

그러면 아래의 presenter와 같이 container에 넘겨준 props를 사용해서 뷰를 만들어준다.

import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";

import Section from "Components/Section";

const Container = styled.div`
  padding: 0px 10px;
`;

const HomePresenter = ({ nowPlaying, popular, upcoming, error, loading }) =>
  loading ? null : (
    <Container>
      {nowPlaying && nowPlaying.length > 0 && (
        <Section title="Now Playing">
          {nowPlaying.map((movie) => movie.title)}
        </Section>
      )}
      {upcoming && upcoming.length > 0 && (
        <Section title="Upcoming Movies">
          {upcoming.map((movie) => movie.title)}
        </Section>
      )}
      {popular && popular.length > 0 && (
        <Section title="popular Movies">
          {popular.map((movie) => movie.title)}
        </Section>
      )}
    </Container>
  );

HomePresenter.propTypes = {
  nowPlaying: PropTypes.array,
  popular: PropTypes.array,
  upcoming: PropTypes.array,
  error: PropTypes.string,
  loading: PropTypes.bool.isRequired,
};

export default HomePresenter;

위 코드에서 보는것과 같이 component인 Section을 가져와서 각각, 상영중, 새영화, 유명한 영화 3가지 테마를

동일한 component로 사용할수가 있고, 이로서 코드의 재사용성이 올라가게 된다.

 

또한, 각각의 Presenter에서만 적용되는 스타일을 별도로 지정할수 있게되서 캡슐화가 가능해진다.