|
1 | | -# FinVibe Frontend |
| 1 | +📌 프로젝트 소개 |
| 2 | + |
| 3 | +- FinVibe는 사용자가 가상 자산으로 실시간 시세 기반 투자 시뮬레이션을 수행할 수 있는 플랫폼입니다. |
| 4 | +- 사용자는 매수/매도 주문을 실행하고, 포트폴리오 평가금액 및 수익률 변화를 즉시 확인할 수 있습니다. |
| 5 | +- 이 프로젝트에서 저는 **금융 시뮬레이션 환경에서 데이터 정합성과 UI 안정성을 동시에 확보하는 프론트엔드 구조 설계**를 담당했습니다. |
| 6 | +--- |
| 7 | + 🎯 핵심 목표 |
| 8 | +- 매수/매도 직후 포트폴리오 상태를 즉시 반영 |
| 9 | +- 실시간 시세 변동에 따른 평가 금액 업데이트 |
| 10 | +- 차트 데이터와 보유 자산 상태 동기화 |
| 11 | +- UI 상태와 서버 상태 충돌 방지 |
| 12 | +--- |
| 13 | + 🧱 기술 스택 |
| 14 | +- **React** |
| 15 | + - 복잡한 투자 UI를 컴포넌트 단위로 모듈화 |
| 16 | +- **TypeScript** |
| 17 | + - 금액/수량/수익률 계산 로직의 타입 안정성 확보 |
| 18 | +- **TanStack Query** |
| 19 | + - 서버 상태 조회/캐싱/동기화 표준화 |
| 20 | + - 매매 요청 이후 재동기화 전략 설계 |
| 21 | + - 캐시 정책으로 중복 요청 감소 |
| 22 | +- **Zustand** |
| 23 | + - 종목 선택, 필터, 모달, 인증, 실시간 시세 등 전역 상태 경량 관리 |
| 24 | + - 서버 상태와 분리해 충돌 방지 |
| 25 | +- **Lightweight Charts** |
| 26 | + - 번들 크기와 렌더링 성능을 고려한 시계열 차트 구성 |
| 27 | +- **Axios Interceptor** |
| 28 | + - 토큰 주입/갱신(Refresh)/재시도 정책 일원화 |
| 29 | +--- |
| 30 | + 🙋 기여 내용 |
| 31 | +- 투자 시뮬레이션 화면 렌더링 구조 설계 |
| 32 | +- 매매 후 포트폴리오 상태 동기화 구조 설계 |
| 33 | +- React Query 기반 서버 상태 관리 표준화 |
| 34 | +- Zustand 기반 전역 클라이언트 상태 구조 설계 |
| 35 | +- 도메인별 API 계층 정리 및 에러 처리 일원화 |
| 36 | +- 차트 렌더링 최적화 |
| 37 | +- Storybook 기반 컴포넌트 개발 환경 구축 |
| 38 | +--- |
| 39 | + 🛠 문제 해결 |
| 40 | + 1) 매매 후 포트폴리오 반영 지연 및 UI 불안정 |
| 41 | + |
| 42 | +| 문제 | 해결 | 결과 | |
| 43 | +| -------------------- | ------------------------ | ------------ | |
| 44 | +| 매수/매도 후 전체 리렌더링 | 영역 단위 상태 분리 | 부분 렌더링 구조 확보 | |
| 45 | +| 매매 반영 전 UI 깜빡임 | `keepPreviousData` 전략 적용 | 체감 지연 최소화 | |
| 46 | +| 시세 데이터와 포트폴리오 동기화 지연 | 재요청 정책 설계 | 데이터 정합성 확보 | |
| 47 | + |
| 48 | +→ 매매가 빈번한 환경에서도 즉각적이고 안정적인 반영 구조를 구현 |
| 49 | + |
| 50 | +--- |
| 51 | + 2) 서버 상태와 UI 상태 혼재로 인한 충돌 |
| 52 | + |
| 53 | +| 문제 | 해결 | 결과 | |
| 54 | +| ---------------- | ------------------------ | ---------- | |
| 55 | +| 매매 후 중복 API 요청 | React Query 캐싱 전략 적용 | 네트워크 효율 개선 | |
| 56 | +| UI 상태와 서버 데이터 혼재 | Server / Client State 분리 | 상태 책임 명확화 | |
| 57 | +| 기능 확장 시 수정 범위 증가 | axios API 계층 일원화 | 유지보수성 향상 | |
| 58 | + |
| 59 | + |
| 60 | +→ 투자 시뮬레이션에서 중요한 데이터 일관성 강화 |
| 61 | + |
| 62 | +--- |
| 63 | + |
| 64 | + 3) 차트 렌더링 부담 |
| 65 | + |
| 66 | +| 문제 | 해결 | 결과 | |
| 67 | +| ------------ | --------------------- | ----------- | |
| 68 | +| 무거운 차트 라이브러리 | lightweight-charts 도입 | 초기 로딩 부담 감소 | |
| 69 | +| 불필요한 리렌더링 | 메모이제이션 적용 | 렌더링 안정성 확보 | |
| 70 | + |
| 71 | + |
| 72 | +→ 시계열 데이터 환경에서 **성능과 가독성의 균형** 확보 |
| 73 | + |
| 74 | +--- |
| 75 | + 🏗 아키텍처 |
| 76 | +본 프로젝트는 상태를 다음 3계층으로 분리한 하이브리드 구조를 사용합니다. |
| 77 | +- **Local UI State**: 컴포넌트 내부 UI 상태 |
| 78 | +- **Global Client State (Zustand)**: 인증/사용자/실시간 시세 등 앱 전역 상태 |
| 79 | +- **Server State (TanStack Query)**: REST API 데이터 조회·캐싱·동기화 |
| 80 | +또한 도메인별 Axios 클라이언트로 API 계층을 분리해 |
| 81 | +`baseURL`, 토큰 주입, 토큰 갱신(Refresh), 재시도 정책을 일관되게 관리했습니다. |
| 82 | +> **실시간 WebSocket 시세 데이터와 REST 거래 데이터를 분리 관리하여, 매매 이후 데이터 정합성과 UI 반응성을 동시에 확보했습니다.** |
| 83 | +
|
| 84 | +설계 의도 |
| 85 | +- 매매 요청 후 Server State 재동기화 |
| 86 | +- UI 상태 독립 관리로 충돌 방지 |
| 87 | +- 단방향 데이터 흐름 유지 |
| 88 | +- 낙관적 업데이트를 고려한 확장 가능한 구조 |
| 89 | + 기대 효과 |
| 90 | +- 매매 반영 즉시성 확보 |
| 91 | +- 포트폴리오 계산 정합성 유지 |
| 92 | +- 예측 가능한 상태 전이 구조 확립 |
| 93 | +--- |
| 94 | + 📊 다이어그램 |
| 95 | + |
| 96 | + 1. 플로우차트 |
| 97 | + <img width="600" height="1200" alt="핀바이브 플로우차트" src="https://github.com/user-attachments/assets/281346b7-4f2f-492d-9047-1e28b39b2bf4" /> |
| 98 | + |
| 99 | + |
| 100 | + 2. 시퀀스 다이어그램 |
| 101 | + <img width="1200" height="631" alt="핀바이브 시퀀스다이어그램" src="https://github.com/user-attachments/assets/97d4d2e7-f382-47e2-a72a-65be263fd6c3" /> |
2 | 102 |
|
3 | | -FinVibe 프로젝트의 프론트엔드 컴포넌트 라이브러리입니다. |
4 | | -Figma 디자인 시스템을 기반으로 구축되었습니다. |
5 | 103 |
|
6 | | -## 기술 스택 |
7 | | - |
8 | | -- **React 19.2** - UI 라이브러리 |
9 | | -- **TypeScript** - 타입 안정성 |
10 | | -- **Vite 7** - 빌드 도구 |
11 | | -- **Tailwind CSS 3** - 유틸리티 우선 CSS 프레임워크 |
12 | | -- **Storybook 8** - 컴포넌트 개발 환경 |
13 | | -- **pnpm** - 패키지 매니저 |
14 | | -- **Figma** - 디자인 시스템 소스 |
15 | | - |
16 | | -## 시작하기 |
17 | | - |
18 | | -### 설치 |
19 | | - |
20 | | -```bash |
21 | | -pnpm install |
22 | | -``` |
23 | | - |
24 | | -### 개발 서버 실행 |
25 | | - |
26 | | -```bash |
27 | | -pnpm dev |
28 | | -``` |
29 | | - |
30 | | -### 스토리북 실행 |
31 | | - |
32 | | -```bash |
33 | | -pnpm storybook |
34 | | -``` |
35 | | - |
36 | | -### 빌드 |
37 | | - |
38 | | -```bash |
39 | | -pnpm build |
40 | | -``` |
41 | | - |
42 | | -## 프로젝트 구조 |
43 | | - |
44 | | -``` |
45 | | -src/ |
46 | | -├── components/ # 재사용 가능한 컴포넌트 |
47 | | -│ ├── Button/ # Button 컴포넌트 |
48 | | -│ │ ├── Button.tsx |
49 | | -│ │ ├── Button.stories.tsx |
50 | | -│ │ └── index.ts |
51 | | -│ └── index.ts # 컴포넌트 export |
52 | | -├── utils/ # 유틸리티 함수 |
53 | | -│ └── cn.ts # 클래스 이름 병합 유틸리티 |
54 | | -├── App.tsx # 메인 앱 컴포넌트 |
55 | | -├── main.tsx # 엔트리 포인트 |
56 | | -└── index.css # 글로벌 스타일 (Tailwind) |
57 | | -``` |
58 | | - |
59 | | -## 컴포넌트 |
60 | | - |
61 | | -### Button |
62 | | - |
63 | | -Figma 디자인 시스템에 기반한 재사용 가능한 버튼 컴포넌트입니다. |
64 | | - |
65 | | -**디자인 소스:** |
66 | | - |
67 | | -- Figma 파일: `FInVibe` |
68 | | -- Small (s): Node ID `503-333` |
69 | | -- Medium (m): Node ID `500-152` |
70 | | -- Large (l): Node ID `540-161` |
71 | | - |
72 | | -**Props:** |
73 | | - |
74 | | -- `variant`: "primary" | "secondary" |
75 | | - - `primary`: 검은색 배경, 흰색 텍스트 (활성 상태) |
76 | | - - `secondary`: 흰색 배경, 회색 테두리, 검은색 텍스트 (비활성 상태) |
77 | | -- `size`: "small" | "medium" | "large" |
78 | | - - `small` (s): 작은 크기 - px-3 py-1.5, text-xs, min-h-28px (Figma: 503-333) |
79 | | - - `medium` (m): 중간 크기 - px-[87px] py-3, text-sm, min-h-36px (Figma: 500-152, 기본값) |
80 | | - - `large` (l): 큰 크기 - px-6 py-4, text-base, min-h-48px, 틸 색상 (Figma: 540-161) |
81 | | -- `fullWidth`: boolean - 전체 너비 사용 여부 |
82 | | -- `loading`: boolean - 로딩 상태 |
83 | | -- `disabled`: boolean - 비활성화 상태 |
84 | | -- `leftIcon`: ReactNode - 왼쪽 아이콘 |
85 | | -- `rightIcon`: ReactNode - 오른쪽 아이콘 |
86 | | - |
87 | | -**디자인 스펙:** |
88 | | - |
89 | | -- 폰트: Noto Sans KR Light, 14px |
90 | | -- 패딩: 12px (상하) × 87px (좌우) |
91 | | -- 보더 반경: 4px |
92 | | -- Line height: 20px |
93 | | - |
94 | | -**사용 예시:** |
95 | | - |
96 | | -```tsx |
97 | | -import { Button } from "./components"; |
98 | | - |
99 | | -function App() { |
100 | | - return ( |
101 | | - <div> |
102 | | - <Button variant="primary">취소</Button> |
103 | | - |
104 | | - <Button variant="secondary">취소</Button> |
105 | | - |
106 | | - {/* 크기 변형 */} |
107 | | - <Button size="small">Small</Button> |
108 | | - <Button size="medium">Medium</Button> |
109 | | - <Button size="large">Large</Button> |
110 | | - |
111 | | - <Button loading>로딩 중...</Button> |
112 | | - |
113 | | - <Button leftIcon={<span>✓</span>}>확인</Button> |
114 | | - </div> |
115 | | - ); |
116 | | -} |
117 | | -``` |
118 | | - |
119 | | -## 스타일링 |
120 | | - |
121 | | -이 프로젝트는 Tailwind CSS를 사용합니다. 커스텀 CSS 파일 대신 Tailwind 유틸리티 클래스를 사용하세요. |
122 | | - |
123 | | -### cn 유틸리티 |
124 | | - |
125 | | -`clsx`와 `tailwind-merge`를 결합한 유틸리티 함수로, Tailwind 클래스 충돌을 해결합니다. |
126 | | - |
127 | | -```tsx |
128 | | -import { cn } from "./utils/cn"; |
129 | | - |
130 | | -const className = cn( |
131 | | - "base-classes", |
132 | | - condition && "conditional-classes", |
133 | | - "override-classes" |
134 | | -); |
135 | | -``` |
136 | | - |
137 | | -## 스토리북 |
138 | | - |
139 | | -모든 컴포넌트는 Storybook 스토리를 포함해야 합니다. |
140 | | - |
141 | | -스토리 파일은 컴포넌트와 같은 디렉토리에 위치합니다: |
142 | | - |
143 | | -- `ComponentName.tsx` - 컴포넌트 |
144 | | -- `ComponentName.stories.tsx` - 스토리북 스토리 |
145 | | - |
146 | | -## 개발 가이드 |
147 | | - |
148 | | -### 새 컴포넌트 추가 |
149 | | - |
150 | | -1. `src/components/ComponentName/` 디렉토리 생성 |
151 | | -2. `ComponentName.tsx` 파일 생성 (Tailwind CSS 사용) |
152 | | -3. `ComponentName.stories.tsx` 파일 생성 |
153 | | -4. `index.ts`에서 export |
154 | | -5. `src/components/index.ts`에 추가 |
155 | | - |
156 | | -### 코드 스타일 |
157 | | - |
158 | | -- TypeScript 사용 |
159 | | -- Tailwind CSS 유틸리티 클래스 사용 |
160 | | -- 컴포넌트는 재사용 가능하게 설계 |
161 | | -- Props는 명확하게 타입 정의 |
162 | | -- JSDoc 주석으로 문서화 |
163 | | - |
164 | | -## 스크립트 |
165 | | - |
166 | | -- `pnpm dev` - 개발 서버 실행 |
167 | | -- `pnpm build` - 프로덕션 빌드 |
168 | | -- `pnpm preview` - 빌드 미리보기 |
169 | | -- `pnpm lint` - ESLint 실행 |
170 | | -- `pnpm storybook` - 스토리북 개발 서버 |
171 | | -- `pnpm build-storybook` - 스토리북 빌드 |
172 | | - |
173 | | -## 라이선스 |
174 | | - |
175 | | -Private |
0 commit comments