아토믹 디자인 패턴을 사용해보자!
Atomic Design
과거 스타트업에서 프론트엔드 개발 인턴을 했었을 당시에 같이 일하셨던 개발자분들이 디자인 패턴에 대해서 말씀해주셨다. 당시에 아무것도 몰랐던 초짜였기에 디자인 패턴에 대해서 잘 몰랐고 그 때 처음으로 아토믹 디자인 패턴에 대해 알게 됐다.
Atomic Design이란?
React는 컴포넌트 단위로 개발을 하게 된다. React를 사용하여 개발을 할 때 가장 중요한 것은 "재 사용성"이다. 아토믹 디자인 패턴은 컴포넌트를 재 사용하면서 효율적으로 만들게 해주는 디자인 패턴이다.
위의 사진을 보는 것처럼 atoms(원자)->molecules(분자)->organisms(유기체)->templates(템플릿)->page(페이지) 순으로 컴포넌트를 만든다.
원자(Atoms)
- 디자인과 기능의 최소 단위
- 다양하게 state를 가지고 있어야 하고 추상적이지만 최대한 포용할 수 있게 설계해야 함.
예시 - > 레이블(Label), 텍스트(Text), 컨테이너(Container), 버튼(Button), 아이콘(Icon)
Molecules(분자)
- 보통 프론트 개발자들이 컴포넌트를 만들 때, 가장 많이 만드는 단위가 분자 수준의 컴포넌트
- 분자는 분자만의 프로퍼티를 가지고 있을 수 있고 이를 활용해 원자에 기능을 만들어 줄 수도 있다
- 분자가 원자의 위치값을 지정하기도 한다
예시 → 입력 폼(Input forms), 네비게이션(Navigation), 카드(Card) 등
Organisms(유기체)
- 분자를 묶어 관리하는 컴포넌트
- 프로젝트 별로 유기체에 해당하는 컴포넌트 단위는 다를 수 있다
- 이를 유기체 단위가 아닌 더 상위 컴포넌트라 할 수 있는 템플릿과 페이지로 관리할 수도 있다
ex) 입력 폼과 함께 헤더를 감싸거나, 여러 카드를 관리하는 그리드
Templates(템플릿)
- 여러 유기체가 모여있지만, 페이지보다는 낮은 단위
- 단, 템플릿에는 Styling이나 Color는 들어가지 않는다
- 템플릿의 역할은 페이지의 그리드를 정해주는 역할 뿐
왜 아토믹 디자인을 써야할까?
개인 프로젝트나 소규모 프로젝트를 진행하거나, 유지보수의 필요가 없는 기능 구현을 목적으로 한 프로젝트라면 구조를 신경 쓸 필요가 없다.
하지만 대형 프로젝트나 앞으로 유지보수의 용이한 코드를 짜기 위해서는 아토믹과 같은 디자인 패턴을 사용해야 한다.
아토믹 디자인 패턴의 장단점
장점
- 재사용 가능한 설계 시스템을 제공
- 유지, 보수 ,수정이 쉽게 가능
- 레이아웃을 이해하기 쉽다.
단점
- 컴포넌트 간의 의존성과 복잡도가 생각보다 까다로울 수 있다.
- 하위 컴포넌트에서 예상치 못한 에러가 발생하면 모든 상위 컴포넌트가 엉망이 되는 일도 발생할 수 있다.
사용 예시)
먼저 이 블로그를 개발할 때도 완전한 아토믹 디자인 패턴을 사용하진 않았지만, 가장 재 사용이 많이 되는 Image 컴포넌트를 Atoms로 만들어서 사용했다.
import React from 'react';
import NextImage, { ImageProps } from 'next/image';
import styled, { css } from 'styled-components';
interface Props extends ImageProps {
src: string;
alt: string;
width?: number;
height?: number;
autoSize?: boolean;
className?: string;
borderRadius?: string;
}
function Image({ src, alt, width, height, autoSize, ...rest }: Props) {
return (
<Wrapper autoSize={autoSize} {...rest}>
<TransitionImage
src={src}
alt={alt}
aria-label={alt}
width={width}
height={height}
layout={autoSize ? 'fill' : 'fixed'}
objectFit={autoSize ? 'contain' : 'cover'}
placeholder="blur"
blurDataURL={src}
draggable={false}
{...rest}
/>
</Wrapper>
);
}
const Wrapper = styled.div<Pick<Props, 'autoSize'>>`
display: flex;
justify-content: center;
width: 100%;
${({ autoSize }) =>
autoSize &&
css`
display: block;
min-height: 215px;
position: relative;
& > span {
position: unset !important;
}
& img {
position: relative !important;
height: auto !important;
}
`};
`;
const TransitionImage = styled(NextImage)`
transition: 0.2s;
border-radius: 24px;
@media screen and (max-width: 768px) {
width: 100%;
height: 85%;
}
`;
export default Image;
위와 같이 다른 곳에서 Image 컴포넌트를 재사용할 수 있도록 만들어줬다.
import { HTMLAttributes, ReactNode } from 'react';
import styled from 'styled-components';
interface Styles {
width?: number;
height?: number;
borderRadius?: number;
}
interface Props extends HTMLAttributes<HTMLButtonElement>, Styles {
children: ReactNode;
className?: string;
}
function Button({ children, className, ...rest }: Props) {
return (
<StyledButton className={className} {...rest}>
{children}
</StyledButton>
);
}
const StyledButton =
styled.button <
Partial <
Props >>
`
all: unset;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
width: ${({ width }) => (width ? width + 'px' : '100%')};
height: ${({ height = 50 }) => (height ? height + 'px' : '100%')};
border-radius: ${({ borderRadius = 10 }) => borderRadius + 'px'};
`;
export default Button;
위와 같이 버튼을 재 사용할 수 있게 atoms 구조로 만들어줬다.
느낀 점
프론트엔드 개발을 할 때 공통적인 디자인을 갖고 있는 버튼,라벨,input을 여러 번 사용하는 경우가 있다.
그때마다 이전에 썼던 코드를 복사해서 사용하는 것보다 재 사용할 수 있게 Atoms구조로 만들어서 사용해보니
코드의 양 적어질 뿐만이 아니라 코드를 사용할 때 너무나 편했다.
하지만, 컴포넌트가 분리되어 있는 상태에서 상위 컨데이너의 사이즈를 정할 수 없는 경우, 미디어쿼리(반응형)을 사용하기 힘들다.
또한 어디까지 atoms으로 하고 어디까지 molecules로 할 지 확실하게 정하지 않는다면 오히려 더 복잡한 디자인 패턴이 될 거라고 생각한다. 그렇기에 함부로 사용하기보단 확실하게 설계를 진행하고 사용해야할 것 같다는 생각이 들었다.