게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌9 - React - 8년 전 등록 / 6년 전 수정

Container와 Component

안녕하세요. 이번 시간에는 ContainerComponent의 차이에 대해 알아보겠습니다.

지난 시간 Login 컴포넌트를 containers 폴더 안에 넣었습니다. Login 컴포넌트가 Container이기 때문입니다. Container도 react 패키지의 Component를 물려받는 컴포넌트의 일종입니다. 하지만 특수한 기능을 하기 때문에 따로 이름붙여졌습니다. ContainerComponent는 공식적으로 구분된 것은 아닙니다. 하지만 사람들은 보통 같은 컴포넌트라도 역할에 따라 다르게 사용합니다. 아무 생각없이 부모에서 내려온 props를 받아쓰는 컴포넌트도 있는 반면(Dumb component라고 부릅니다) 리덕스와 소통하면서 앱의 상태(리덕스 state)를 제어하는 컴포넌트도 있습니다(Smart component 또는 Container라고 부릅니다)

리덕스를 사용할 때에는 특히 ContainerComponent를 구분해주는 게 좋습니다. Container는 앱의 상태를 관리하기 때문에 앱의 상태가 자주 바뀔수록 그에 따라 빈번하게 업데이트가 일어납니다. 그래서 필요없는 부분에 업데이트가 일어나지 않게 하려면 Container과 Component를 구분해주어야 합니다. 리액트는 컴포넌트 단위로 업데이트를 하기 때문이죠. Container가 업데이트되어도 그 아래 Component와 상관이 없다면 업데이트가 일어나지 않습니다.

이전 시간의 Login 컴포넌트를 Container와 Component로 쪼개볼까요? 지난 시간의 컴포넌트를 기능별로 분리하면 됩니다. Container는 리덕스와 연결하고, 리덕스로부터 받은 상태 데이터를 Component로 전달해주면 됩니다. 실제적으로 사용자와 상호작용하는 것은 Component의 역할입니다. 폴더로 container와 component 두 개를 만들고 각각 안에 다음을 추가합니다.

container/Login.jsx

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { login } from '../action/user.js';
import LoginForm from '../component/LoginForm.jsx';

class Login extends Component {
  render() {
    const { user } = this.props;
    return (
      <LoginForm
        isLoggedIn={user.isLoggedIn}
        login={(id, pw) => dispatch(login(id, pw))}
      />
    );
  }
}
function mapStateToProps(state) {
  return { user: state.user };
}
export default connect(mapStateToProps)(Login);

component/LoginForm.jsx

import React, { Component } from 'react';

class LoginForm extends Component {
  handleSubmit = (e) => {
    e.preventDefault();
    const { login } = this.props;
    const id = this.id.value;
    const pw = this.password.value;
    login(id, pw);
  };

  render() {
    const { isLoggedIn, login } = this.props;
    return (
      isLoggedIn ?
        <div>로그인 성공</div> :
        <form onSubmit={this.handleSubmit}>
          <label>
            <span>아이디</span>
            <input ref={(c) => { this.id = c; }} />
          </label>
          <label>
            <span>비밀번호</span>
            <input type="password" ref={(c) => { this.password = c; }} />
          </label>
        </form>
    );
  }
};
export default LoginForm;

두 개로 나누어진 컴포넌트들은 지난 시간의 코드와 같은 기능을 합니다. 같은 기능을 하는데 굳이 왜 두 개로 나누었냐고 물으신다면, 여러 장점이 있기 때문입니다.

일단, 유지보수가 쉽습니다. 각각의 컴포넌트는 고유의 기능을 합니다. container/Login는 리덕스로부터 데이터를 받고 action을 실행하는 역할만을 전담하고, component/LoginForm은 리덕스의 state가 어떻든 상관하지 않고 그저 사용자와 상호작용한 후 로그인 정보를 받아서 Container로 넘겨줍니다. 각각 자신의 역할이 명확하기 때문에 어떤 에러가 발생했을 시 어떤 컴포넌트를 고쳐야하는 지 확인하기 쉽습니다.

그리고 성능이 좋아집니다. 위의 예에서는 컴포넌트를 두 개로밖에 나누지 않았기 때문에 그 효과가 미미합니다. 하지만, 만약 컴포넌트가 복잡해짐에 따라 여러 개로 나누게 된다면, 리액트의 특성상 업데이트가 컴포넌트 별로 일어나기 때문에 잘게 쪼갤수록 불필요한 컴포넌트의 업데이트를 방지할 수 있습니다.

물론 단점도 있습니다. 코드를 분리했기 때문에 생기는 단점입니다. 코드가 분리되어 용량이 조금 증가되고(매우 미미하지만 이렇게 분리된 코드가 점점 많아지면 영향을 미칩니다), 코드의 양이 많아지는 만큼 추적이 어려워질 수 있습니다. 또한 Container에서 Component로 데이터를 보내는 부분이 중복의 느낌이 있습니다. props를 전달하는 과정에서 생기는 중복입니다.

이렇게 코드를 분리하는 데는 장단점이 있기 때문에 중간 타협점을 잘 찾아서 분리하는 게 좋습니다. 무턱대고 분리해서도 안 되고, 너무 잘게 분리해서도 안 됩니다. 다음 시간에는 리액트성능에 관한 이야기를 해보겠습니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

2개의 댓글이 있습니다.
3년 전
훅을쓰면서 container, component로 나누는 필요가 적어젔다는게,, 어떠한 의미에서 나눌 필요가 없어졌다는 말씀이실까요??
3년 전
데이터를 가져오는 용도의 container, 화면을 표시하는 용도의 component로 분리하는 것이 아니라 그냥 모두 다 컴포넌트로 만들고 hooks를 필요한 때 쓰면 됩니다.
4년 전
혹시 Nodebird 강좌에서 올드버전을 수강했는데요 거기선 Component, Container로 폴더를 나눴는데, 신버전 에서는 그렇지 않은 것 같더라구요, 혹시 이유가 있을까요 ?
4년 전
훅 쓰면서 굳이 container, components로 나누는 필요가 많이 적어졌습니다. 다만 재사용이 빈번한 작은 컴포넌트들은 components로 관리하는 게 좋습니다.