V2 컴포넌트 및 Unified 폼 컴포넌트 결합도 분석 보고서
작성일: 2026-01-26
목적: 컴포넌트 간 결합도 분석 및 느슨한 결합 전환 가능성 평가
1. 분석 대상 컴포넌트 목록
1.1 V2 컴포넌트 (18개)
| # |
컴포넌트 |
경로 |
주요 용도 |
| 1 |
v2-aggregation-widget |
v2-aggregation-widget/ |
데이터 집계 표시 |
| 2 |
v2-button-primary |
v2-button-primary/ |
기본 버튼 (저장/삭제/모달 등) |
| 3 |
v2-card-display |
v2-card-display/ |
카드 형태 데이터 표시 |
| 4 |
v2-category-manager |
v2-category-manager/ |
카테고리 트리 관리 |
| 5 |
v2-divider-line |
v2-divider-line/ |
구분선 |
| 6 |
v2-location-swap-selector |
v2-location-swap-selector/ |
출발지/도착지 선택 |
| 7 |
v2-numbering-rule |
v2-numbering-rule/ |
채번 규칙 표시 |
| 8 |
v2-pivot-grid |
v2-pivot-grid/ |
피벗 테이블 |
| 9 |
v2-rack-structure |
v2-rack-structure/ |
렉 구조 표시 |
| 10 |
v2-repeat-container |
v2-repeat-container/ |
리피터 컨테이너 |
| 11 |
v2-repeat-screen-modal |
v2-repeat-screen-modal/ |
반복 화면 모달 |
| 12 |
v2-section-card |
v2-section-card/ |
섹션 카드 |
| 13 |
v2-section-paper |
v2-section-paper/ |
섹션 페이퍼 |
| 14 |
v2-split-panel-layout |
v2-split-panel-layout/ |
분할 패널 레이아웃 |
| 15 |
v2-table-list |
v2-table-list/ |
테이블 리스트 |
| 16 |
v2-table-search-widget |
v2-table-search-widget/ |
테이블 검색 위젯 |
| 17 |
v2-tabs-widget |
v2-tabs-widget/ |
탭 위젯 |
| 18 |
v2-text-display |
v2-text-display/ |
텍스트 표시 |
| 19 |
v2-unified-repeater |
v2-unified-repeater/ |
통합 리피터 |
1.2 Unified 폼 컴포넌트 (11개)
| # |
컴포넌트 |
파일 |
주요 용도 |
| 1 |
UnifiedInput |
UnifiedInput.tsx |
텍스트/숫자/이메일 등 입력 |
| 2 |
UnifiedSelect |
UnifiedSelect.tsx |
선택박스/라디오/체크박스 |
| 3 |
UnifiedDate |
UnifiedDate.tsx |
날짜/시간 입력 |
| 4 |
UnifiedRepeater |
UnifiedRepeater.tsx |
리피터 (테이블 형태) |
| 5 |
UnifiedLayout |
UnifiedLayout.tsx |
레이아웃 컨테이너 |
| 6 |
UnifiedGroup |
UnifiedGroup.tsx |
그룹 컨테이너 (카드/탭/접기) |
| 7 |
UnifiedHierarchy |
UnifiedHierarchy.tsx |
계층 구조 표시 |
| 8 |
UnifiedList |
UnifiedList.tsx |
리스트 표시 |
| 9 |
UnifiedMedia |
UnifiedMedia.tsx |
파일/이미지/비디오 업로드 |
| 10 |
UnifiedBiz |
UnifiedBiz.tsx |
비즈니스 컴포넌트 |
| 11 |
UnifiedFormContext |
UnifiedFormContext.tsx |
폼 상태 관리 컨텍스트 |
2. 결합도 분석 결과
2.1 결합도 유형 분류
| 유형 |
설명 |
문제점 |
| 직접 Import |
다른 모듈을 직접 import하여 사용 |
변경 시 영향 범위 큼 |
| CustomEvent |
window.dispatchEvent로 이벤트 발생/수신 |
암묵적 의존성, 타입 안전성 부족 |
| 전역 상태 (window.__) |
window 객체에 전역 변수 저장 |
네임스페이스 충돌, 테스트 어려움 |
| Context API |
React Context로 상태 공유 |
상대적으로 안전하지만 범위 확장 시 주의 |
2.2 V2 컴포넌트 결합도 상세
2.2.1 높은 결합도 (High Coupling) - 우선 개선 대상
| 컴포넌트 |
buttonActions Import |
CustomEvent 사용 |
window.__ 사용 |
결합도 점수 |
| v2-button-primary |
✅ 직접 Import |
4개 발생 |
❌ |
🔴 8/10 |
| v2-table-list |
❌ |
16개 수신/발생 |
4개 사용 |
🔴 9/10 |
v2-button-primary 상세:
// 직접 의존
import { ButtonActionExecutor, ButtonActionContext } from "@/lib/utils/buttonActions";
// CustomEvent 발생
window.dispatchEvent(new CustomEvent("refreshTable"));
window.dispatchEvent(new CustomEvent("closeEditModal"));
window.dispatchEvent(new CustomEvent("saveSuccessInModal"));
v2-table-list 상세:
// 전역 상태 사용
window.__relatedButtonsTargetTables
window.__relatedButtonsSelectedData
// CustomEvent 발생
window.dispatchEvent(new CustomEvent("tableListDataChange", { ... }));
// CustomEvent 수신
window.addEventListener("refreshTable", handleRefreshTable);
window.addEventListener("related-button-register", ...);
window.addEventListener("related-button-unregister", ...);
window.addEventListener("related-button-select", ...);
2.2.2 중간 결합도 (Medium Coupling)
| 컴포넌트 |
buttonActions Import |
CustomEvent 사용 |
window.__ 사용 |
결합도 점수 |
| v2-repeat-container |
❌ |
5개 수신/발생 |
❌ |
🟠 6/10 |
| v2-split-panel-layout |
❌ |
3개 수신/발생 |
❌ |
🟠 5/10 |
| v2-aggregation-widget |
❌ |
14개 수신 |
❌ |
🟠 6/10 |
| v2-tabs-widget |
❌ |
2개 |
❌ |
🟠 4/10 |
v2-repeat-container 상세:
// CustomEvent 수신
window.addEventListener("beforeFormSave", handleBeforeFormSave);
window.addEventListener("repeaterDataChange", handleDataChange);
window.addEventListener("tableListDataChange", handleDataChange);
v2-aggregation-widget 상세:
// CustomEvent 수신 (다수)
window.addEventListener("tableListDataChange", handleTableListDataChange);
window.addEventListener("repeaterDataChange", handleRepeaterDataChange);
window.addEventListener("selectionChange", handleSelectionChange);
window.addEventListener("tableSelectionChange", handleSelectionChange);
window.addEventListener("rowSelectionChange", handleSelectionChange);
window.addEventListener("checkboxSelectionChange", handleSelectionChange);
2.2.3 낮은 결합도 (Low Coupling) - 독립적
| 컴포넌트 |
buttonActions Import |
CustomEvent 사용 |
window.__ 사용 |
결합도 점수 |
| v2-pivot-grid |
❌ |
0개 |
window.open만 |
🟢 2/10 |
| v2-card-display |
❌ |
1개 수신 |
❌ |
🟢 2/10 |
| v2-category-manager |
❌ |
2개 (ConfigPanel) |
❌ |
🟢 2/10 |
| v2-divider-line |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-location-swap-selector |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-numbering-rule |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-rack-structure |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-section-card |
❌ |
1개 (ConfigPanel) |
❌ |
🟢 1/10 |
| v2-section-paper |
❌ |
1개 (ConfigPanel) |
❌ |
🟢 1/10 |
| v2-table-search-widget |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-text-display |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-repeat-screen-modal |
❌ |
0개 |
❌ |
🟢 1/10 |
| v2-unified-repeater |
❌ |
0개 |
❌ |
🟢 1/10 |
2.3 Unified 폼 컴포넌트 결합도 상세
| 컴포넌트 |
buttonActions Import |
CustomEvent 사용 |
window.__ 사용 |
결합도 점수 |
| UnifiedRepeater |
❌ |
7개 수신/발생 |
2개 사용 |
🔴 8/10 |
| UnifiedFormContext |
❌ |
3개 발생 |
❌ |
🟠 4/10 |
| UnifiedInput |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedSelect |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedDate |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedLayout |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedGroup |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedHierarchy |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedList |
❌ |
0개 (TableList 래핑) |
❌ |
🟢 2/10 |
| UnifiedMedia |
❌ |
0개 |
❌ |
🟢 1/10 |
| UnifiedBiz |
❌ |
0개 |
❌ |
🟢 1/10 |
UnifiedRepeater 상세:
// 전역 상태 사용
window.__unifiedRepeaterInstances = new Set();
window.__unifiedRepeaterInstances.add(targetTableName);
// CustomEvent 수신
window.addEventListener("repeaterSave", handleSaveEvent);
window.addEventListener("beforeFormSave", handleBeforeFormSave);
window.addEventListener("componentDataTransfer", handleComponentDataTransfer);
window.addEventListener("splitPanelDataTransfer", handleSplitPanelDataTransfer);
UnifiedFormContext 상세:
// CustomEvent 발생 (레거시 호환)
window.dispatchEvent(new CustomEvent("beforeFormSave", { detail: eventDetail }));
window.dispatchEvent(new CustomEvent("afterFormSave", { detail: { ... } }));
3. 주요 결합 지점 시각화
┌─────────────────────────────────────────────────────────────────────────┐
│ buttonActions.ts (7,145줄) │
│ ⬇️ 직접 Import │
│ v2-button-primary ───────────────────────────────────────────────┐
│ │
└─────────────────────────────────────────────────────────────────────────┘
│
│ CustomEvent
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ Event Bus (현재: window) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ refreshTable │ │beforeFormSave│ │tableListData │ │
│ │ │ │ │ │ Change │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
└─────────│──────────────────│──────────────────│─────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────────┐
│v2-table │ │v2-repeat │ │v2-aggregation │
│ -list │ │-container │ │ -widget │
└───────────┘ └───────────┘ └───────────────┘
│ │
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│Unified │ │Unified │
│Repeater │ │FormContext│
└───────────┘ └───────────┘
4. 이벤트 매트릭스
4.1 이벤트 발생 컴포넌트
| 이벤트명 |
발생 컴포넌트 |
용도 |
refreshTable |
v2-button-primary, buttonActions |
테이블 데이터 새로고침 |
closeEditModal |
v2-button-primary, buttonActions |
수정 모달 닫기 |
saveSuccessInModal |
v2-button-primary, buttonActions |
저장 성공 알림 (연속 등록) |
beforeFormSave |
UnifiedFormContext, buttonActions |
저장 전 데이터 수집 |
afterFormSave |
UnifiedFormContext |
저장 완료 알림 |
tableListDataChange |
v2-table-list |
테이블 데이터 변경 알림 |
repeaterDataChange |
UnifiedRepeater |
리피터 데이터 변경 알림 |
repeaterSave |
buttonActions |
리피터 저장 요청 |
openScreenModal |
v2-split-panel-layout |
화면 모달 열기 |
refreshCardDisplay |
buttonActions |
카드 디스플레이 새로고침 |
4.2 이벤트 수신 컴포넌트
| 이벤트명 |
수신 컴포넌트 |
처리 내용 |
refreshTable |
v2-table-list, v2-split-panel-layout |
데이터 재조회 |
beforeFormSave |
v2-repeat-container, UnifiedRepeater |
formData에 섹션 데이터 추가 |
tableListDataChange |
v2-aggregation-widget, v2-repeat-container |
집계 재계산, 데이터 동기화 |
repeaterDataChange |
v2-aggregation-widget, v2-repeat-container |
집계 재계산, 데이터 동기화 |
repeaterSave |
UnifiedRepeater |
리피터 데이터 저장 실행 |
selectionChange |
v2-aggregation-widget |
선택 기반 집계 |
componentDataTransfer |
UnifiedRepeater |
컴포넌트 간 데이터 전달 |
splitPanelDataTransfer |
UnifiedRepeater |
분할 패널 데이터 전달 |
refreshCardDisplay |
v2-card-display |
카드 데이터 재조회 |
5. 전역 상태 사용 현황
| 전역 변수 |
사용 컴포넌트 |
용도 |
위험도 |
window.__unifiedRepeaterInstances |
UnifiedRepeater, buttonActions |
리피터 인스턴스 추적 |
🟠 중간 |
window.__relatedButtonsTargetTables |
v2-table-list |
관련 버튼 대상 테이블 |
🟠 중간 |
window.__relatedButtonsSelectedData |
v2-table-list, buttonActions |
관련 버튼 선택 데이터 |
🟠 중간 |
window.__dataRegistry |
v2-table-list (v1/v2) |
테이블 데이터 레지스트리 |
🟠 중간 |
6. 결합도 요약 점수
6.1 V2 컴포넌트 (18개)
| 결합도 수준 |
개수 |
컴포넌트 |
| 🔴 높음 (7-10점) |
2개 |
v2-button-primary, v2-table-list |
| 🟠 중간 (4-6점) |
4개 |
v2-repeat-container, v2-split-panel-layout, v2-aggregation-widget, v2-tabs-widget |
| 🟢 낮음 (1-3점) |
12개 |
나머지 |
6.2 Unified 컴포넌트 (11개)
| 결합도 수준 |
개수 |
컴포넌트 |
| 🔴 높음 (7-10점) |
1개 |
UnifiedRepeater |
| 🟠 중간 (4-6점) |
1개 |
UnifiedFormContext |
| 🟢 낮음 (1-3점) |
9개 |
나머지 |
6.3 전체 결합도 분포
전체 29개 컴포넌트
높은 결합도 (🔴): 3개 (10.3%)
├── v2-button-primary
├── v2-table-list
└── UnifiedRepeater
중간 결합도 (🟠): 5개 (17.2%)
├── v2-repeat-container
├── v2-split-panel-layout
├── v2-aggregation-widget
├── v2-tabs-widget
└── UnifiedFormContext
낮은 결합도 (🟢): 21개 (72.5%)
└── 나머지 모든 컴포넌트
7. 장애 영향 분석
7.1 현재 구조에서의 장애 전파 경로
v2-button-primary 오류 발생 시:
├── buttonActions.ts 영향 → 모든 저장/삭제 기능 중단
├── refreshTable 이벤트 미발생 → 테이블 갱신 안됨
└── closeEditModal 이벤트 미발생 → 모달 닫기 안됨
v2-table-list 오류 발생 시:
├── tableListDataChange 미발생 → 집계 위젯 업데이트 안됨
├── related-button 이벤트 미발생 → 관련 버튼 비활성화
└── 전역 상태 오염 가능성
UnifiedRepeater 오류 발생 시:
├── beforeFormSave 처리 실패 → 리피터 데이터 저장 누락
├── repeaterSave 수신 실패 → 저장 요청 무시
└── 전역 인스턴스 레지스트리 오류
7.2 장애 격리 현황
| 컴포넌트 |
장애 시 영향 범위 |
격리 수준 |
| v2-button-primary |
저장/삭제 전체 |
❌ 격리 안됨 |
| v2-table-list |
집계/관련버튼 |
❌ 격리 안됨 |
| UnifiedRepeater |
리피터 저장 |
❌ 격리 안됨 |
| v2-aggregation-widget |
자신만 |
✅ 부분 격리 |
| v2-repeat-container |
자신만 |
✅ 부분 격리 |
| 나머지 21개 |
자신만 |
✅ 완전 격리 |
8. 느슨한 결합 전환 권장사항
8.1 1단계: 인프라 구축 (1-2일)
-
V2 EventBus 생성
- 타입 안전한 이벤트 시스템
- 에러 격리 (Promise.allSettled)
- 구독/발행 패턴
-
V2 ErrorBoundary 생성
- 컴포넌트별 장애 격리
- 폴백 UI 제공
- 재시도 기능
8.2 2단계: 핵심 컴포넌트 분리 (3-4일)
| 우선순위 |
컴포넌트 |
작업 내용 |
| 1 |
v2-button-primary |
buttonActions 의존성 제거, 독립 저장 서비스 |
| 2 |
v2-table-list |
전역 상태 제거, EventBus 전환 |
| 3 |
UnifiedRepeater |
전역 상태 제거, EventBus 전환 |
8.3 3단계: 이벤트 통합 (2-3일)
| 기존 이벤트 |
신규 이벤트 |
변환 방식 |
refreshTable |
v2:table:refresh |
EventBus 발행 |
beforeFormSave |
v2:form:save:before |
EventBus 발행 |
tableListDataChange |
v2:table:data:change |
EventBus 발행 |
repeaterSave |
v2:repeater:save |
EventBus 발행 |
8.4 4단계: 레거시 제거 (1-2일)
window.__ 전역 변수 → Context API 또는 Zustand
- 기존 CustomEvent → V2 EventBus로 완전 전환
- buttonActions.ts 경량화 (7,145줄 → 분할)
9. 예상 효과
9.1 장애 격리
| 현재 |
전환 후 |
| 한 컴포넌트 오류 → 연쇄 실패 |
한 컴포넌트 오류 → 해당만 실패 표시 |
| 저장 실패 → 전체 중단 |
저장 실패 → 부분 저장 + 에러 표시 |
9.2 유지보수성
| 현재 |
전환 후 |
| buttonActions.ts 7,145줄 |
여러 서비스로 분리 (각 500줄 이하) |
| 암묵적 이벤트 계약 |
타입 정의된 이벤트 |
| 전역 상태 오염 위험 |
Context/Store로 관리 |
9.3 테스트 용이성
| 현재 |
전환 후 |
| 통합 테스트만 가능 |
단위 테스트 가능 |
| 모킹 어려움 |
EventBus 모킹 용이 |
10. 구현 현황 (2026-01-26 업데이트)
10.1 V2 Core 인프라 (✅ 완료)
다음 핵심 인프라가 구현되었습니다:
| 모듈 |
경로 |
설명 |
상태 |
| V2 EventBus |
lib/v2-core/events/EventBus.ts |
타입 안전한 이벤트 시스템 |
✅ 완료 |
| V2 이벤트 타입 |
lib/v2-core/events/types.ts |
모든 이벤트 타입 정의 |
✅ 완료 |
| V2 ErrorBoundary |
lib/v2-core/components/V2ErrorBoundary.tsx |
컴포넌트별 에러 격리 |
✅ 완료 |
| 레거시 어댑터 |
lib/v2-core/adapters/LegacyEventAdapter.ts |
CustomEvent ↔ EventBus 브릿지 |
✅ 완료 |
| V2 Core 초기화 |
lib/v2-core/init.ts |
앱 시작 시 초기화 |
✅ 완료 |
10.2 컴포넌트 마이그레이션 현황
| 컴포넌트 |
V2 EventBus 적용 |
ErrorBoundary 적용 |
레거시 지원 |
상태 |
| v2-button-primary |
✅ |
✅ |
✅ |
완료 |
| v2-table-list |
✅ |
- |
✅ |
완료 |
| UnifiedRepeater |
✅ |
- |
✅ |
완료 |
10.3 아키텍처 특징
점진적 마이그레이션 지원:
- 레거시
window.dispatchEvent 이벤트와 V2 EventBus 이벤트가 양방향 브릿지로 연결됨
- 기존 코드 수정 없이 새 시스템 도입 가능
- 모든 V2 이벤트는 자동으로 레거시 CustomEvent로도 발행됨
에러 격리:
- V2ErrorBoundary로 감싼 컴포넌트는 에러 발생 시 해당 컴포넌트만 에러 UI 표시
- 다른 컴포넌트는 정상 작동 유지
- 재시도 버튼으로 복구 가능
10.4 사용 방법
// 이벤트 발행
import { v2EventBus, V2_EVENTS } from "@/lib/v2-core";
v2EventBus.emit(V2_EVENTS.TABLE_REFRESH, {
tableName: "item_info",
target: "single",
});
// 이벤트 구독
const unsubscribe = v2EventBus.subscribe(
V2_EVENTS.TABLE_REFRESH,
(payload) => {
console.log("테이블 새로고침:", payload.tableName);
},
{ componentId: "my-component" }
);
// 정리
useEffect(() => {
return () => unsubscribe();
}, []);
11. 결론
11.1 현재 상태 요약
- 전체 29개 컴포넌트 중 72.5%(21개)는 이미 낮은 결합도를 가지고 있어 독립적으로 동작
- 핵심 문제 컴포넌트 3개 (v2-button-primary, v2-table-list, UnifiedRepeater) 마이그레이션 완료
- **buttonActions.ts (7,145줄)**는 추후 분할 예정 (현재는 동작 유지)
11.2 달성 목표
✅ V2 Core 인프라 구축 완료
- 타입 안전한 EventBus
- 컴포넌트별 ErrorBoundary
- 레거시 호환 어댑터
- 앱 초기화 연동
11.3 다음 단계
- buttonActions.ts 분할 - 서비스별 모듈 분리
- 나머지 중간 결합도 컴포넌트 마이그레이션 (v2-repeat-container, v2-split-panel-layout 등)
- 전역 상태 (window.__) 제거 - Context API 또는 Zustand로 전환
부록 A: 파일 위치 참조
frontend/
├── lib/
│ ├── registry/
│ │ └── components/
│ │ ├── v2-aggregation-widget/
│ │ ├── v2-button-primary/
│ │ ├── v2-card-display/
│ │ ├── v2-category-manager/
│ │ ├── v2-divider-line/
│ │ ├── v2-location-swap-selector/
│ │ ├── v2-numbering-rule/
│ │ ├── v2-pivot-grid/
│ │ ├── v2-rack-structure/
│ │ ├── v2-repeat-container/
│ │ ├── v2-repeat-screen-modal/
│ │ ├── v2-section-card/
│ │ ├── v2-section-paper/
│ │ ├── v2-split-panel-layout/
│ │ ├── v2-table-list/
│ │ ├── v2-table-search-widget/
│ │ ├── v2-tabs-widget/
│ │ ├── v2-text-display/
│ │ └── v2-unified-repeater/
│ └── utils/
│ └── buttonActions.ts (7,145줄)
└── components/
└── unified/
├── UnifiedInput.tsx
├── UnifiedSelect.tsx
├── UnifiedDate.tsx
├── UnifiedRepeater.tsx
├── UnifiedLayout.tsx
├── UnifiedGroup.tsx
├── UnifiedHierarchy.tsx
├── UnifiedList.tsx
├── UnifiedMedia.tsx
├── UnifiedBiz.tsx
└── UnifiedFormContext.tsx
부록 B: V2 Core 파일 구조 (구현됨)
frontend/lib/v2-core/
├── index.ts # 메인 내보내기
├── init.ts # 앱 초기화
├── events/
│ ├── index.ts
│ ├── types.ts # 이벤트 타입 정의
│ └── EventBus.ts # 이벤트 버스 구현
├── components/
│ ├── index.ts
│ └── V2ErrorBoundary.tsx # 에러 바운더리
└── adapters/
├── index.ts
└── LegacyEventAdapter.ts # 레거시 브릿지
부록 C: 이벤트 타입 정의 (구현됨)
전체 이벤트 타입은 frontend/lib/v2-core/events/types.ts에 정의되어 있습니다.
주요 이벤트:
| 이벤트 |
설명 |
v2:table:refresh |
테이블 새로고침 |
v2:table:data:change |
테이블 데이터 변경 |
v2:form:save:collect |
폼 저장 전 데이터 수집 |
v2:modal:close |
모달 닫기 |
v2:modal:save:success |
모달 저장 성공 |
v2:repeater:save |
리피터 저장 |
v2:component:error |
컴포넌트 에러 |