리액트 디자인 패턴에 관한 글을 여럿 찾아 보면, 조금 더 구조 설계(특히 폴더 구조 관리)와 business logic - view logic의 관리 부분에 초점이 맞춰진 느낌이다.
2015년 Dan Abramov가 처음 소개한 패턴으로, 가장 기본적이고 유명한 패턴이다.
function TotalComponent() {
const [titleState, setTitleState] = useState("some title");
const data = fetchSomething();
const onClose = () => {
// do something
}
// ...
return (
<div>
<div>** This component only awares about UI</div>
<div>{titleState}</div>
<div>{data.description}</div>
<button onClick={onClose}>Close</button>
{* ... *}
</div>
)
}
function PresentationalComponent({ title, description, onClose, ...props }) {
return (
<div>
<div>** This component only awares about UI</div>
<div>{title}</div>
<div>{description}</div>
<button onClick={onClose}>Close</button>
{* ... *}
</div>
)
}
function ContainerComponent() {
const [titleState, setTitleState] = useState("some title");
const data = fetchSomething();
const onClose = () => {
// do something
}
// ...
return (
<PresentationalComponent
title={titleState}
description={data.description}
onClose={onClose}
// other props
/>
)
}
보편적인 폴더 구조는 다음과 같은 식이다.
components
├── Login
│ ├── LoginContainer.tsx
│ ├── LoginPresentation.tsx
│ └── Login.style.ts
└── User
├── UserContainer.tsx
├── UserPresentation.tsx
└── User.style.ts
...
hooks의 도입 이후, Dan Abramov가 새롭게 제안한 방식이다.
function TotalComponent() {
const [titleState, setTitleState] = useState("some title");
const data = fetchSomething();
const onClose = () => {
// do something
}
// ...
return (
<div>
<div>** This component only awares about UI</div>
<div>{titleState}</div>
<div>{data.description}</div>
<button onClick={onClose}>Close</button>
{* ... *}
</div>
)
}
function useTitle() {
const [title, setTitle] = useState("some title");
const data = fetchSomething();
const onClose = () => {
// do something
};
// ...
return { title, setTitle, data, onClose };
}
function TotalComponent() {
const { title, setTitle, data, onClose } = useTitle();
return (
<div>
<div>** This component only awares about UI</div>
<div>{titleState}</div>
<div>{data.description}</div>
<button onClick={onClose}>Close</button>
{* ... *}
</div>
)
}
2013년 Brad Frost에 의해 처음으로 제시되었다. 원래는 디자인 시스템을 위한 패턴이다. (디자인 시스템에서 컴포넌트를 효율적으로 구성하는 방식에 대한 내용이다.)
보편적인 폴더 구조는 다음과 같은 식이다.
components
├── ui
│ ├── atoms // 가장 작은 단위의 컴포넌트
│ │ ├── Input
│ │ └── Checkbox
│ ├── molecules // atom을 여러 개 조합한 컴포넌트
│ │ └── LoginInputs
│ └── organisms // molecule과 atom을 조합하여 만든 컴포넌트
│ └── LoginComponent
├── templates // 컴포넌트를 넣어 사용할 레이아웃
│ ├── LoginTemplate
│ ├── UserTemplate
│ └── BookTemplate
└── pages // 가장 큰 단위의, templates에 atom, molecule, organism 등을 주입한 컴포넌트
└── Login
리디에서는 Atomic Design Pattern을 도입하여 프로젝트를 구성하였는데, 5단계는 너무 많다는 생각이 들어 Atom - Block - Page의 3단계로만 구분하여 사용하였다고 한다.
components // UI모듈을 구조화해서 관리
├── atoms // 버튼, 체크박스 등 가장 작은 기능을 담당하는 컴포넌트
│ ├── Button
│ │ └── index.tsx
│ ├── CheckBox
│ └── ...
├── blocks // 페이지보다 작은 단위면서 공통적으로 사용할 수 있는 컴포넌트
└── pages // 가장 큰 단위의 페이지
├── Home // 라우트 단위 directory
│ ├── index.tsx // 최상위 코드, container 형태의 로직만 담음
│ ├── Home.tsx // UI 모듈이 필요할 시 별도로 분리
│ ├── OtherChild.tsx
│ └── styles.ts // style 관련 코드
└── ...
ducks // redux 모듈을 관리. ducks패턴을 사용하여 reducer&action을 같이 관리
├── auth.ts
├── home.ts
├── index.ts
├── series.ts
└── ...
hocs // common hocs
hooks // custom hooks
View Asset Component.
기존의 View 컴포넌트에서 jsx와 관련된 영역을 props object로 추상화한 뒤, VAC로 분리해서 개발하는 방식이다. 비즈니스 로직 뿐 아니라 UI 로직에서도 렌더링 관심사를 명확하게 분리하는 것이 목적이다.
function NumberBox() {
const [value, setValue] = useState(0);
return (
<div>
<button disabled={value < 1} onClick={() => setValue(value - 1)}>-</button>
<span>{value}</span>
<button disabled={value > 9} onClick={() => setValue(value + 1)}>+</button>
</div>
);
}
function NumberBox() {
const [value, setValue] = useState(0);
const props = {
value,
disabledDecrease: value < 1,
disabledIncrease: value > 9,
onDecrease: () => setValue(value - 1),
onIncrease: () => setValue(value + 1),
};
// JSX 대신 VAC
return <NumberBoxView {...props} />;
}
function NumberBoxView= ({ value, disabledDecrease, disabledIncrease, onIncrease, onDecrease }) => (
<div>
<button disabled={disabledDecrease} onClick={onDecrease}>-</button>
<span>{value}</span>
<button disabled={disabledIncrease} onClick={onIncrease}>+</button>
</div>
);
이외에도 이 글을 작성하는 데 직접적으로 참고하진 않았지만, 관련해서 다양한 글을 읽어보며 흥미롭게 읽었던 글 몇 개를 같이 남긴다.