디자인 패턴
정의
소프트웨어를 만드는데 있어 공통적으로 발생하는 문제에 대해 재사용 가능한 해결책
종류
- 소프트웨어 디자인 패턴
- 생성패턴 - 의존성 주입, 프로토타입, 싱글톤 …
- 구조패턴 - 모듈, 프록시, 어댑터 …
- 행동패턴 - 옵저버, 템플릿 메소드, 상태 …
- 어플리케이션 디자인 패턴
- MVC
- MVVM
- MVP
- Flux
- 컴포넌트 디자인 패턴
- Presentational and Container Component Pattern
- Atomic Design Pattern
- VAC 패턴
Presentational and Container Component Pattern
정의
데이터 처리와 데이터 출력을 분리하는 패턴

Container Components
- 주로 fetch 가 이루어짐
- 연관있는 서브 컴포넌트 렌더링
- Markup 이나 style 이 없음
- 다른 컴포넌트에 callback 함수나 데이터를 전달해 줄 수 있음
- stateful 한 경향을 가지고 있는 컴포넌트
Presentational Components
- 화면에 보여지는 것만을 담당하는 Components
- Markup 과 style 을 포함
- props를 통해 데이터나 callback 을 받을 수 있음
- 뷰에 필요한 state를 가지고 있을 수 있음
- stateless한 경향을 가지는 컴포넌트
예시
Container Component
// CommentListContainer.js
import React from "react";
import CommentList from "./CommentList";
class CommentListContainer extends React.Component {
constructor() {
super();
this.state = { comments: [] };
}
componentDidMount() {
fetch("/my-comments.json")
.then(res => res.json())
.then(comments => this.setState({ comments }));
}
render() {
return <CommentList comments={this.state.comments} />;
}
}
Presenter Component
// CommentListPresenter.js
import React from "react";
const Commentlist = comments => (
<ul>
{comments.map(({ body, author }) => (
<li>
{body}-{author}
</li>
))}
</ul>
장점
- 관심사의 분리가 더 분명해짐
- 기존 관심사 분리 + UI & state 분리
- 재사용성을 높일 수 있음
- 여러 가지 state를 prop으로 받더라도 같은 presentational component를 사용할 수 있음
- Markup 작업이 편함
- 이 패턴을 위해 Layout Component를 별도로 추출하게 되면 여러 Container를 작성하는 작업을 피할 수 있음
한계
- 현재 hook으로 인해 이미 stateful 한 로직을 분리해주고 있어 해당패턴을 강제할 필요는 없음
Atomic Design Pattern
정의
디자인 요소들을 나누어 파악하고 이 요소들이 조합되는 과정을 통해서 디자인을 구성하는 방식
단계

- Atoms
- 하나의 구성 요소. 본인 자체의 스타일만 가지고 있으며 다른 곳에 영향을 미치는 스타일은 적용되지 않아야 합니다. 원자는 form labels, inputs, buttons와 같은 basic hmtl elements를 포함합니다.

- Molecules
- Atoms가 모여서 만들어지는 하나의 구성 요소
- Atom 단위인 input label, input, buttoms를 합쳐 새로운 의미있는 단위를 만들 수 있습니다. 실제로 무언가 동작을 할 수 있게 됩니다.

- Organisms
- 서로 동일하거나 다른 molecules로 구성될 수 있음
- 유기체는 로고, 메인 내비게이션, 검색, 소셜 미디어 채널리스트와 같은 다양한 컴포넌트(molecules)로 구성될 수 있습니다.

- Templates
- 유기체들을 모아 템플릿으로 생성, 스타일링에 집중한 단위
- Templates의 중요한 특성은 페이지의 최종 내용보다는 페이지의 기본 내용 구조에 초점을 맞춘다는 것입니다.

- Pages
- 페이지는 실제 대표적인 콘텐츠가 배치된 UI의 모습을 보여주는 템플릿의 특정 인스턴스입니다.
- Pages 단위에서 어플리케이션 상태 관리(리덕스, 모벡스 등등)가 이루어져야 합니다.
- 하지만 분자, 유기체, 템플릿 단위에서 컴포넌틑를 동작시키기 위한 상태를 관리하는건 괜찮습니다. (input과 onChange를 useState로 관리하는등의 상태관리)

단점, 한계
- 프로젝트 설계 시간이 오래 소요됨
- 최소단위인 atmos 구성 단위에 따라 효율성이 달라짐
- 최상위 구조인 pages에서 상태관리 로직을 관리한다면 props를 통해 하위 구조에 계속 내려줘야하기때문에 prop drilling으로 인해 복잡해지고 유지보수가 어려워질 수 있음
VAC
정의
View Asset Component
VAC 패턴은 View 컴포넌트에서 JSX 영역을 Props Object로 추상화하고, JSX를 VAC로 분리해서 개발하는 설계 방법입니다.

VAC 특징
- 반복이나 조건부 노출, 스타일 제어와 같은 렌더링과 관련된 처리만을 수행합니다.
- 오직 props를 통해서만 제어되며 스스로의 상태를 관리하거나 변경하지 않는 stateless 컴포넌트입니다.
- 이벤트에 함수를 바인딩할 때 어떠한 추가 처리도 하지 않습니다
VAC는 state를 가질 수 없지만 state를 가진 컴포넌트를 자식으로 가지는 것은 가능합니다. 이 경우 VAC는 부모 컴포넌트와 자식 컴포넌트 중간에서 개입하지 않고 단순히 props를 전달하는 역할만 합니다.
예제
일반적인 설계
const SpinBox = () => {
const [value, setValue] = useState(0);
return (
<div>
<button onClick={() => setValue(value - 1)}>-</button>
<span>{value}</span>
<button onClick={() => setValue(value + 1)}>+</button>
</div>
);
};
props object 정의
- View 컴포넌트에서 JSX(vue - template) 를 추상화한 props object를 생성하고 사용할 상태정보나 이벤트 핸들러 정의
const SpinBox = () => {
const [value, setValue] = useState(0);
// JSX를 추상화한 Props Object
const props = {
value,
onDecrease: () => setValue(value - 1),
onIncrease: () => setValue(value + 1),
};
// JSX의 유무는 중요하지 않음
return <div></div>;
};
jsx를 VAC로 분리
- JSX 영역을 분리하여 VAC로 만듦
- props object 속성을 참고하여 VAC의 props를 정의
// VAC
const SpinBoxView = ({ value, onIncrease, onDecrease }) => (
<div>
<button onClick={onDecrease}>-</button>
<span>{value}</span>
<button onClick={onIncrease}>+</button>
</div>
);
// View Component
const SpinBox = () => {
const [value, setValue] = useState(0);
const props = {
value,
onDecrease: () => setValue(value - 1),
onIncrease: () => setValue(value + 1),
};
// JSX를 VAC로 교체
return <SpinBoxView {...props} />;
};
props object를 사용하는 이유
- props object와 VAC를 사용하지 않고 직접 선언하여 UI 기능 의존성을 줄이는것도 가능
- 하지만, 변수나 함수들이 많아지면 어떤것을 JSX에서 사용하는지 파악이 어려워 디버깅도 어려워짐
- 또한, view 컴포넌트 내에서 JSX를 관리하고 있어 간단한 상태처리의 경우 무의식중에 바로 JSX 에서 적용할 수 있음
// View Component
const SpinBox = () => {
const [value, setValue] = useState(0);
// JSX에서 사용할 값을 미리 선언하여 JSX에 적용
const onDecrease = () => setValue(value - 1);
const onIncrease = () => setValue(value + 1);
return (
<div>
<button onClick={onDecrease}>-</button>
<span>{value}</span>
<button onClick={onIncrease}>+</button>
</div>
);
};
presentational 컴포넌트와 VAC 차이
- VAC 패턴은 Container 컴포넌트에 로직을 위임하는 설계 방식을 따르기 때문에 Presentational과 Container 컴포넌트 패턴의 한 종류라고 볼 수 있음
- 두 컴포넌트의 근본적인 차이는 컴포넌트가 View 로직(UI 기능, 상태 관리)을 가질수 있는지 여부
- Presentational 컴포넌트는 상황에 따라 View와 관련된 state를 가지고 스스로 상태를 제어하는 것을 허용하지만, VAC는 stateless 컴포넌트로 스스로의 상태를 제어하지 않고 항상 부모 컴포넌트에서 Props Object를 통해 관리합니다
presentationalVAC
목적 | View 로직(UI 기능, 상태 관리)과 렌더링(JSX)의 관심사 분리 | 비즈니스 로직과 View의 관심사 분리가 목적 |
부모 컴포넌트 | Container 컴포넌트 → 비즈니스 로직을 관리하고 Presentational 컴포넌트를 제어 |
view 컴포넌트 → VAC의 Container 컴포넌트 역할을 하며 JSX를 추상화한 Props Object를 관리하여 VAC를 제어 |
자식 컴포넌트 | Presentational 컴포넌트 -> View 로직(UI 기능, 상태 관리)과 렌더링을 담당 |
VAC → JSX, Style을 관리하여 렌더링 처리 |
현재 사용중인 구조에서 개선점
- composables 의 오용
- composable은 stateful 한 로직중 재사용 가능한 로직을 캡슐화 시킨 함수를 가리킴
- 현재는 stateful 한 로직이 포함된것은 맞지만 재사용 가능성이 전혀 없는 로직들이 대부분
- composables을 props object나 container 형태로 대체
- stateful 한 코드와 UI 코드의 분리
- 의존성이 낮아져서 재사용이 가능함
- 보다 분명한 의도를 가진 구조로 처음 보아도 구조 파악이 쉬움
참고문서
'Front-end' 카테고리의 다른 글
turborepo (0) | 2024.07.18 |
---|---|
Intersection Observer API (0) | 2023.07.18 |
Popover api (1) | 2023.06.13 |
testing (0) | 2022.12.12 |