🔮 디자인 패턴이란
디자인 패턴이란 프로그램을 개발하는 과정에서 사용되는 설계 패턴들을 정의한 것입니다.
과거의 웹 사이트보다 훨씬 복잡해지고 다양한 개발 프레임워크를 사용하는 지금,
효율적인 웹 사이트의 구축이나 유지보수를 위해 컴포넌트 단위의 활용은 더욱 중요한 사항이 되었습니다.
따라서 컴포넌트를 구성하는 방법에 대한 다양한 디자인 패턴이 생겨나게 되었으며,
패턴마다 장단점이 존재하므로 프로젝트의 특성에 맞는 디자인 패턴을 선택해야 합니다.
리액트의 주요한 디자인 패턴은 다음과 같습니다.
- 컴포넌트 기반 아키텍처(component-based architecture)
- 상태 관리 패턴(state management pattern)
- 프레젠테이션-컨테이너 패턴(presentation-container pattern)
- 고차 컴포넌트 패턴(HOC, higher-order component pattern)
- 아토믹 패턴(atomic pattern)
- 커스텀 훅 패턴(custom hooks pattern)
- etc.
이 중에서도 프레젠테이션-컨테이너 패턴에 대해 알아보겠습니다.
🔮 프레젠테이션-컨테이너 패턴
리액트의 디자인 패턴 중에서 가장 기본적인 디자인 패턴으로, 크게 UI를 렌더링하는 '프레젠테이션 컴포넌트'와 데이터 로직을 수행하는 '컨테이너 컴포넌트'로 분리하여 구현합니다.
- 프레젠테이션 컴포넌트(presentation component)
- 해당 컴포넌트는 데이터를 직접 관리하지 않습니다.
- 컨테이너 컴포넌트에게 props를 통해 데이터를 전달받습니다.
- 주요 기능은 전달받은 데이터를 화면에 렌더링하는 것이며, 이를 위한 스타일 시트를 포함합니다.
- 컨테이너 컴포넌트(container component)
- 해당 컴포넌트는 화면에 아무것도 렌더링하지 않습니다.
- 프레젠테이션 컴포넌트에게 데이터를 props로 전달해줍니다.
- 주요 기능은 데이터를 프레젠테이션 컴포넌트에게 전달해주는 것이며, 화면에 보여주는 것이 없으므로 스타일 시트 또한 포함하지 않습니다.
- API 호출, state 관리, 이벤트 처리 등의 작업을 수행합니다.
컴포넌트 별로 맡는 역할을 분리하여 각각의 컴포넌트가 명확한 기능과 책임을 가집니다.
이로 인해 얻을 수 있는 프레젠테이션-컴포넌트 디자인 패턴의 장점은 다음과 같습니다.
- 컴포넌트 간 의존도가 낮아집니다.
- 프레젠테이션 컴포넌트를 재사용할 수 있습니다.
- 컨테이너 컴포넌트에서 정의한 state를 다른 컴포넌트들에게 props로 전달하여 상태를 공유할 수 있습니다.
만약 역할을 분리하지 않고 하나의 컴포넌트 안에 뷰와 로직을 담당하는 코드를 구현한다면, 해당 컴포넌트는 재사용이 어려워지고 의존도가 높아지게 됩니다.
참고로 좋은 컴포넌트는 낮은 결합도(coupling)와 높은 응집도(cohesion)를 가집니다.
낮은 결합도를 가진 컴포넌트는 각각의 코드가 서로 얽혀있지 않고 독립적으로 분리되어 있습니다.
높은 응집도를 가진 컴포넌트는 하나의 목적을 위한 유사한 내용의 코드들이 모여 있습니다.
🔮 활용
검색 기능을 위한 예시 코드를 통해 프레젠테이션-컨테이너 패턴을 적용해보겠습니다.
검색어를 입력하고 버튼을 누르면 검색어가 콘솔창에 출력되는 코드입니다.
먼저 디자인 패턴을 적용하지 않은 코드입니다.
function SearchForm() {
// state
const [searchKey, setSearcKey] = useState();
// 메서드들
function onChange(event) {
setSearcKey(event.target.value);
}
function onSubmit(event) {
event.preventDefault();
console.log(searchKey);
}
// UI 렌더링을 위한 JSX
return (
<form onSubmit={onSubmit}>
<label>제목</label>
<input type="text" value={searchKey} onChange={onChange} name="searchKey"/>
<button type="submit">검색</button>
</form>
);
}
export default SearchForm;
디자인 패턴을 적용하기 전에는 하나의 컴포넌트 안에 state와 메서드들, 그리고 UI 렌더링에 필요한 코드가 모두 담겨 있습니다.
다음은 디자인 패턴을 적용한 코드입니다.
// 프레젠테이션 컴포넌트(SearchFormView.jsx)
function SearchFormView({ searchKey, onChange, onSubmit }) {
// UI 렌더링을 위한 JSX
return (
<form onSubmit={onSubmit}>
<label>제목</label>
<input type="text" value={searchKey} onChange={onChange} name="searchKey"/>
<button type="submit">검색</button>
</form>
);
}
export default SearchFormView;
프레젠테이션 컴포넌트에서는 컨테이너 컴포넌트에게 props로 받아온 데이터를 가지고 렌더링에 필요한 코드만 작성하였습니다.
// 컨테이너 컴포넌트 (SearchFormContainer.jsx)
function SearchFormContainer() {
// state
const [searchKey, setSearcKey] = useState();
// 메서드들
function onChange(event) {
setSearcKey(event.target.value);
}
function onSubmit(event) {
event.preventDefault();
console.log(searchKey);
}
return (
<SearchFormView
searchKey={searchKey}
onChange={onChange}
onSubmit={onSubmit}
/>
);
}
export default SearchFormContainer;
컨테이너 컴포넌트에서는 state와 메서드를 정의하여 프레젠테이션 컴포넌트에게 props로 넘겨주고, 렌더링과 관련된 코드는 작성되지 않았습니다.
읽어주셔서 감사합니다:)