Container-Presenter

JavaScript React

Container / Presenter

Container / Presentational 패턴이란, 소스코드를 자바스크립트(기능)과 JSX(UI)로 나누는 방법을 의미한다.

/* 이 부분이 Container */
export default function UsernamePage() {            
  const [username, setUsername] = useState();
  const handleChangeUsername = (event) => {
    const username = event.target.value;
    setUsername(username)
  }

  /* 이 부분이 Presenter */
  return (
    <div>
      <label>Username</label>
      <input type="text" onChange={handleChangeUsername}>
    <div/>
  )
}

파일 나누기

파일 구조는 아래와 같게 지정한다. 파일을 나눠도 실행될 때는 하나로 합쳐져서 실행될 수 있도록 한다.

├── parentFolder
│   ├── Username.container.js
│   ├── Username.presenter.js
~~~~~~~   
~~~~~~~
│   ├── Username.queries.js // 등 다른 종속 파일들이 들어갈 수 있다
│   ├── Username.styles.js

각 파일당 코드 구조는 위의 코드를 각 부분에 맞게 위치시키면 된다.

/* Username.container.js */
import UsernameUI from "./Username.presenter" // import Presenter here

export default function Username() {
  const [username, setUsername] = useState();
  const handleChangeUsername = (event) => {
    const username = event.target.value;
    setUsername(username)
  } 
  return (
    <UsernameUI/>
  )
}
/* Username.presenter.js */
export default function UsernameUI() {
  return (
    <div>
      <label>Username</label>
      <input type="text" onChange={handleChangeUsername}>
    <div/>
  )
}

Props

컴포넌트를 두개로 나누면서 데이터와 기능의 연결고리가 끊어지게 된다. 이를 연결해주는 것이 props의 역할이며, props란 부모 컴포넌트가 자식 컴포넌트에게 물려주는(단방향) 변수 및 함수를 의미한다. 부모 컴포넌트가 props를 물려줄 때는 객체로 묶어서 넘기게 된다. React Docs

/* Username.container.js */
import UsernameUI from "./Username.presenter" // import Presenter here

export default function Username() {
  const [username, setUsername] = useState();
  const handleChangeUsername = (event) => {
    const username = event.target.value;
    setUsername(username)
  } 
  return (
    /* 
      <input type="text" onChange={handleChangeUsername} 
      의 onChange 이벤트를 더이상 받을 수 없으므로 props로 전달해야 함.
    */
    <UsernameUI changeUsername={handleChangeUsername}/>
  )
}
/* Username.presenter.js */
/* Parent(container)에서 전달받은 handleChangeUsername는 props의 객체 아래로 들어옴 */
export default function UsernameUI(props) {
  return (
    <div>
      <label>Username</label>
      <input type="text" onChange={props.changeUsername}>
    <div/>
  )
}

+ 한 컴포넌트의 UI 및 기능을 한 파일에 담는 게 특징인 Vue에서는 아래와 같이 구현된다.

<!-- UsernameInput.vue --> 
<template>
  <div>
    <label>Username</label>
    <input :value="username" @change="$emit('update:username', $event.target.value)" /> 
  </div>
</template>

<script> 
  export default { props: ['username'], emits: ['update:username'] } 
</script>