디자인패턴: Presentation & Container

뮤진·2023년 5월 6일
0
post-thumbnail

Intro

지난 포스팅으로 뷰 로직과 비즈니스 로직을 분리하는 아키텍처에 대한 글을 작성하며 디자인패턴을 공부해봐야겠다고 생각했다.

이번에 친한 디자이너와 함께 개인 프로젝트를 하게 되는데 디자인 패턴을 적용해보려고 한다.

디자인 패턴을 공부하면 관심사 분리에 대한 말이 많이 등장한다.
단일 책임 원칙(하나의 모듈은 하나의 기능만 담당) 을 준수하기 때문에 관심사 분리를 하게되며,결국 이를 어떻게 분리해야 하는지를 다루는 것이 디자인패턴이다.

리액트에서 재사용성을 고려하여 컴포넌트를 나눈다고 하지만 초반에는 그저 UI를 기준으로 나누는 것이 전부인 줄 알고 몰두했었다.
점점 공부를 하면서 아키텍처와 디자인패턴에 대해서도 알게되었고 규모있는 프로젝트에서는 프로젝틑 구조를 짜는 것 부터 리액트 컴포넌트를 어떻게 분리할지가 매우 중요할 것 같다는 생각이 들었다.

그래서 가장 기초적이라고 하는 패턴인 Presentation & Container Pattern에 대해 공부하고 프로젝트에 적용해보려고한다.


Presentation & Container Pattern

이 패턴을 처음 공부하면서 가장 먼저 한 생각은
'지난번 공부한 프론트엔드 아키텍처랑 같은 내용이잖아?' 였다.
그렇다면 왜 아키텍처와 디자인 패턴으로 나뉘는지 찾아보았다.

디자인 패턴과 아키텍처 패턴은 소프트웨어공학에서 발생하는 문제를 해결한다는 점에서 비슷하지만 디자인 패턴은 특정 문제를 해결하기 위한 방법이고, 아키텍처 패턴은 전체적인 소프트웨어에서 발생하는 문제들을 해결하기 위한 방법이라고한다.
조만간 이 차이에 대해서 더 다루어봐야겠다.

이 패턴은 어플리케이션에서 UI를 제공하는 부분과 데이터를 처리하는 부분으로 나뉘는 간단한 사실에서 출발한다.

Presentation Component 사용자에게 보여주는 부분을 담당 (UI, render) 하며

Container Component 데이터를 처리하는 로직의 대부분의 것들을 다룬다.
특히 API호출, 데이터 조작, 이벤트 처리 작업 등을 한다.

👉 예제

정말 간단한 Todo 컴포넌트가 있다.

function Todo() {
  const [todos, setTodos] = useState();
  const fetchTodos = async () => {
  	const {data} = await axios.get(GET_TODOS_URL)
    setTodos(data.todos)
  }
  
  useEffect(() => fetchTodos(), []);

  return (
      <ul>
        {todos?.map((todo) => (
          <li>
            {todo}
          </li>
        )}
      </ul>
  )
}

이 컴포넌트에는 컨테이너와 프레젠테이션 컴포넌트가 같이 있음을 알 수 있다.
로직을 다루는 부분을 컨테이너로 분리하고,
렌더를 담당하는 부분을 프레젠테이션으로 분리한다.

이때 React 커뮤니티에서 사용되는 보편적인 방법으로,
컨테이너 컴포넌트 이름뒤에는 Container를 추가한다.

따라서 아래 코드처럼 나눌 수 있다.

// Presentational Component
const Todo = ({todos}) => {
  return (
      <ul>
        {todos?.map((todo) => (
          <li>
            {todo}
          </li>
        )}
      </ul>
  )
}
// Container Component
function TodoContainer() {
  const [todos, setTodos] = useState();
  const fetchTodos = async () => {
  	const {data} = await axios.get(GET_TODOS_URL)
    setTodos(data.todos)
  }
  
  useEffect(() => fetchTodos(), []);
  
  return <Todo todos={todos} />
}

Presentation Component - how things look

렌더링 부분을 담당하고 있는 이 Presentation 컴포넌트에서는 데이터를 가져오거나 수정하지 않는다. 따로 state를 가지거나 외부로부터 데이터를 받아오는 동작을 하지 않는다. (핸들러함수도 props로 받아와서 처리할 수 있음)

따라서 이 컴포넌트는 props에 대한 pure function이다.
(stateless functional component)

이렇게 Presentation 컴포넌트의 관심사를 철저히 화면에 표시해주는 부분으로 국한시켰기 때문에 재사용성이 높을 수 밖에 없다.

Container Component - how things work

이 컴포넌트는 state를 가지며 데이터를 받아오거나 수정, 핸들링한 데이터를 props로 주입해주는 등의 동작을 수행한다.

다른 Todos를 가져오고 싶다거나 데이터를 수정하고 싶다면 모두 Container 컴포넌트만 수정하면 되는 것이다 !

디렉토리

보통 Container Component는 /Container
Presentation Component는 /component에 배치한다고 한다.

Presentation 컴포넌트는 재사용성이 아주 높은 컴포넌트이기때문에
Container 컴포넌트와 인접해서 배치하지 않아도 괜찮을 것 같다.


Outro

디자인패턴을 통해 코드를 깔끔하게 유지하고, 자원소모를 줄일 수 있다.
그러나 충분히 간결하고 크게 손 댈 필요가 없는 코드라면 (냄새가 나지 않는 코드라면 ^_^) 패턴을 지키려 굳이 하위 컴포넌트를 많이 만들 필요는 없다.

아무런 이유 없이 이 패턴을 적용한다면 결국 더 많은 파일과 하위 컴포넌트를 만들게 되면서 복잡도만 증가하게 될 것 같다.

케이스를 잘 판단해서 패턴을 적용하고
결국 유지보수성의 편의성을 위한 것이라는 목적을 잃지 말고 작성해야겠다 ✨

profile
프론트엔드 공부기록 🫶 기록을 통해 성장하기

0개의 댓글