# Frontend 구조 설계서 & 상세 설계
## 1. 개요
- 목적: React 기반 SPA(단일 페이지 애플리케이션)의 확장성과 재사용성을 고려한 FE 구조 설계
- 대상: UI 컴포넌트 단위의 상세 설계 및 구현 가이드
---
## 2. FE 아키텍처 구조 설계
### 2.1 폴더 구조
.
├─ public/
│ └─ index.html
├─ src/
│ ├─ assets/ # 이미지, 폰트, 스타일 등 정적 자산
│ ├─ components/ # 재사용 가능한 UI 컴포넌트
│ │ ├─ Button/
│ │ │ ├─ Button.tsx
│ │ │ ├─ Button.styles.ts
│ │ │ └─ index.ts
│ │ ├─ Modal/
│ │ │ ├─ Modal.tsx
│ │ │ ├─ Modal.styles.ts
│ │ │ └─ index.ts
│ │ └─ Card/
│ │ ├─ Card.tsx
│ │ ├─ Card.styles.ts
│ │ └─ index.ts
│ ├─ pages/ # 라우트 단위 페이지 컴포넌트
│ ├─ hooks/ # 커스텀 훅
│ ├─ contexts/ # React Context
│ ├─ services/ # API 호출 모듈
│ ├─ utils/ # 범용 유틸 함수
│ ├─ App.tsx
│ └─ index.tsx
├─ package.json
└─ tsconfig.json
### 2.2 컴포넌트 분류
1. **Atoms**: 버튼, 입력창 등 가장 작은 단위
2. **Molecules**: 아톰을 조합한 간단한 UI 블록 (e.g. 검색 바)
3. **Organisms**: 모듈 단위의 큰 구조 (e.g. 헤더, 푸터)
4. **Templates / Pages**: 실제 화면
---
## 3. 상세 설계: UI 컴포넌트
### 3.1 Button 컴포넌트
#### 3.1.1 설계 개요
- **역할**: 클릭 가능한 버튼
- **사용 시나리오**: 폼 제출, 페이지 이동, 액션 트리거
#### 3.1.2 Props 정의
```ts
// src/components/Button/Button.tsx
export interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary' | 'danger';
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
onClick?: () => void;
}
// src/components/Button/Button.styles.ts
import { css } from '@emotion/react';
export const baseStyle = css`
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
`;
export const variantStyles = {
primary: css`
background-color: #0070f3;
color: #fff;
`,
secondary: css`
background-color: #eaeaea;
color: #000;
`,
danger: css`
background-color: #e00;
color: #fff;
`,
};
export const sizeStyles = {
small: css`padding: 4px 8px; font-size: 0.8rem;`,
medium: css`padding: 8px 16px; font-size: 1rem;`,
large: css`padding: 12px 24px; font-size: 1.2rem;`,
};
// src/components/Button/Button.tsx
import React from 'react';
import { ButtonProps } from './Button.props';
import { baseStyle, variantStyles, sizeStyles } from './Button.styles';
const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
size = 'medium',
disabled = false,
onClick,
}) => (
<button
css={[baseStyle, variantStyles[variant], sizeStyles[size]]}
disabled={disabled}
onClick={onClick}
>
{children}
</button>
);
export default Button;
// src/components/Modal/Modal.tsx
export interface ModalProps {
isOpen: boolean;
onClose: () => void;
title?: string;
children: React.ReactNode;
}
// src/components/Modal/Modal.styles.ts
import { css } from '@emotion/react';
export const overlayStyle = css`
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex; align-items: center; justify-content: center;
`;
export const contentStyle = css`
background: #fff;
border-radius: 8px;
max-width: 500px;
width: 100%;
padding: 16px;
`;
// src/components/Modal/Modal.tsx
import React from 'react';
import { ModalProps } from './Modal.props';
import { overlayStyle, contentStyle } from './Modal.styles';
import Button from '../Button';
const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children }) => {
if (!isOpen) return null;
return (
<div css={overlayStyle}>
<div css={contentStyle}>
{title && <h2>{title}</h2>}
<div>{children}</div>
<Button variant="secondary" size="small" onClick={onClose}>
닫기
</Button>
</div>
</div>
);
};
export default Modal;
// src/components/Card/Card.props.ts
export interface CardProps {
header?: React.ReactNode;
footer?: React.ReactNode;
children: React.ReactNode;
}
// src/components/Card/Card.tsx
import React from 'react';
import { CardProps } from './Card.props';
import { css } from '@emotion/react';
const containerStyle = css`
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
`;
const headerStyle = css`margin-bottom: 12px; font-weight: 600;`;
const footerStyle = css`margin-top: 12px; font-size: 0.9rem; color: #666;`;
const Card: React.FC<CardProps> = ({ header, footer, children }) => (
<div css={containerStyle}>
{header && <div css={headerStyle}>{header}</div>}
<div>{children}</div>
{footer && <div css={footerStyle}>{footer}</div>}
</div>
);
export default Card;
위 설계를 기반으로 실제 프로젝트에 적용하면, 모듈화된 FE 구조와 일관된 UI 컴포넌트를 손쉽게 관리·확장할 수 있습니다.