--- description: globs: alwaysApply: false --- # Univer CE 초기화 및 인스턴스 관리 규칙 ## **핵심 원칙** - **단일 인스턴스**: 컴포넌트당 하나의 Univer 인스턴스만 유지 - **중복 방지**: 플러그인 등록과 초기화 중복 실행 방지 - **적절한 정리**: 메모리 누수 방지를 위한 dispose 패턴 ## **초기화 패턴** ### ✅ DO: 안전한 초기화 ```typescript // 인스턴스 존재 확인 후 초기화 useEffect(() => { if (!univerRef.current) { initializeUniver(); } }, []); // 처리 중 상태 확인으로 중복 방지 const processFile = useCallback(async (file: File) => { if (isProcessing) { console.log("이미 파일 처리 중입니다."); return; } setIsProcessing(true); // ... 파일 처리 로직 }, [isProcessing]); ``` ### ❌ DON'T: 중복 초기화 유발 ```typescript // 조건 없는 초기화 (중복 실행 위험) useEffect(() => { initializeUniver(); // 매번 실행됨 }, []); // 상태 확인 없는 처리 const processFile = useCallback(async (file: File) => { // isProcessing 확인 없이 바로 실행 setIsProcessing(true); }, []); ``` ## **인스턴스 관리** ### ✅ DO: 적절한 dispose 패턴 ```typescript // 새 인스턴스 생성 전 기존 인스턴스 정리 if (univerRef.current) { univerRef.current.dispose(); univerRef.current = null; } // 컴포넌트 언마운트 시 정리 useEffect(() => { return () => { if (univerRef.current) { univerRef.current.dispose(); univerRef.current = null; } }; }, []); ``` ### ❌ DON'T: 메모리 누수 위험 ```typescript // dispose 없이 새 인스턴스 생성 univerRef.current = univer.createUnit(UnitType.UNIVER_SHEET, workbook); // 정리 로직 없는 컴포넌트 // useEffect cleanup 누락 ``` ## **파일 처리 최적화** ### ✅ DO: 중복 처리 방지 ```typescript // 파일 입력 초기화로 재선택 가능 finally { if (event.target) { event.target.value = ""; } } // 의존성 배열에 상태 포함 const processFile = useCallback(async (file: File) => { // ... 로직 }, [isProcessing]); ``` ## **REDI 중복 로드 방지** ### ✅ DO: 라이브러리 중복 확인 ```typescript // 개발 환경에서 REDI 중복 로드 경고 모니터링 // 동일한 라이브러리 버전 사용 확인 // 번들링 시 중복 제거 설정 ``` ### ❌ DON'T: 무시하면 안 되는 경고 ```typescript // REDI 중복 로드 경고 무시 // 서로 다른 버전의 동일 라이브러리 사용 // 번들 중복 제거 설정 누락 ``` ## **디버깅 및 로깅** ### ✅ DO: 의미있는 로그 ```typescript console.log("초기화할 워크북 데이터:", workbookData); console.log("Univer 인스턴스 생성 완료"); console.log("플러그인 등록 완료"); ``` ### ❌ DON'T: 과도한 로깅 ```typescript // 매 렌더링마다 로그 출력 // 민감한 데이터 로깅 // 프로덕션 환경 디버그 로그 유지 ``` ## **성능 최적화** - **useCallback**: 이벤트 핸들러 메모이제이션 - **의존성 최적화**: 필요한 의존성만 포함 - **조건부 실행**: 불필요한 재실행 방지 - **메모리 관리**: 적절한 dispose와 cleanup 이 규칙을 따르면 Univer CE가 안정적으로 작동하고 메모리 누수 없이 파일 처리가 가능합니다. # Univer CE REDI 중복 로드 오류 완전 방지 규칙 ## **문제 원인** - Univer CE의 REDI 시스템에서 "Identifier already exists" 오류는 동일한 서비스가 중복으로 등록될 때 발생 - 컴포넌트 재마운트, HMR(Hot Module Reload), 또는 동시 초기화 시도로 인한 중복 인스턴스 생성 - 브라우저 캐시에 잔존하는 이전 REDI 등록 정보 ## **필수 해결 패턴** ### **1. 전역 인스턴스 관리자 패턴 사용** ```typescript // ✅ DO: 모듈 레벨에서 단일 인스턴스 보장 let globalUniver: Univer | null = null; let globalInitializing = false; let globalDisposing = false; const UniverseManager = { async createInstance(container: HTMLElement): Promise { if (globalUniver) return globalUniver; if (globalInitializing) { // 초기화 완료까지 대기 while (globalInitializing) { await new Promise((resolve) => setTimeout(resolve, 50)); } if (globalUniver) return globalUniver; } globalInitializing = true; try { // Univer 인스턴스 생성 로직 globalUniver = new Univer({...}); return globalUniver; } finally { globalInitializing = false; } }, getInstance(): Univer | null { return globalUniver; } }; // ❌ DON'T: 컴포넌트마다 개별 인스턴스 생성 const MyComponent = () => { const [univer, setUniver] = useState(null); useEffect(() => { const newUniver = new Univer({...}); // 중복 생성 위험 setUniver(newUniver); }, []); }; ``` ### **2. 상태 기반 중복 초기화 방지** ```typescript // ✅ DO: 강화된 상태 확인으로 중복 실행 완전 차단 const processFile = useCallback(async (file: File) => { if ( isProcessing || UniverseManager.isInitializing() || UniverseManager.isDisposing() ) { console.log("처리 중이거나 상태 변경 중입니다."); return; // 중복 실행 차단 } setIsProcessing(true); try { // 파일 처리 로직 } finally { setIsProcessing(false); } }, [isProcessing]); // 의존성 배열에 상태 포함 // ❌ DON'T: 간단한 상태 확인만으로는 불충분 const processFile = useCallback(async (file: File) => { if (isProcessing) return; // 다른 상태는 확인하지 않음 }, []); ``` ### **3. 컴포넌트 마운트 시 기존 인스턴스 재사용** ```typescript // ✅ DO: 기존 전역 인스턴스 우선 재사용 useEffect(() => { const existingUniver = UniverseManager.getInstance(); if (existingUniver && !UniverseManager.isInitializing()) { setIsInitialized(true); return; // 재사용으로 중복 초기화 방지 } if (containerRef.current && !UniverseManager.isInitializing()) { initializeUniver(); } }, []); // ❌ DON'T: 매번 새로운 초기화 시도 useEffect(() => { initializeUniver(); // 기존 인스턴스 무시하고 새로 생성 }, []); ``` ### **4. 디버깅을 위한 전역 상태 제공** ```typescript // ✅ DO: 전역 디버그 객체로 상태 추적 가능 if (typeof window !== "undefined") { (window as any).__UNIVER_DEBUG__ = { getGlobalUniver: () => globalUniver, getGlobalInitializing: () => globalInitializing, clearGlobalState: () => { globalUniver = null; globalInitializing = false; globalDisposing = false; }, }; } ``` ### **5. 기존 워크북 정리 시 API 호환성 처리** ```typescript // ✅ DO: try-catch로 API 버전 차이 대응 try { const existingUnits = (univer as any).getUnitsForType?.(UniverInstanceType.UNIVER_SHEET) || []; for (const unit of existingUnits) { (univer as any).disposeUnit?.(unit.getUnitId()); } } catch (error) { console.log("기존 워크북 정리 시 오류 (무시 가능):", error); } // ❌ DON'T: API 호환성 고려하지 않은 직접 호출 univer.getUnitsForType(UniverInstanceType.UNIVER_SHEET); // 버전에 따라 실패 가능 ``` ## **브라우저 캐시 완전 삭제** ### **개발 환경에서 REDI 오류 발생 시 필수 작업** ```bash # ✅ DO: 브라우저 캐시 완전 삭제 rm -rf node_modules/.vite && rm -rf dist # 추가적으로 브라우저에서: # - 강제 새로고침 (Ctrl+Shift+R 또는 Cmd+Shift+R) # - 개발자 도구 > Application > Storage > Clear storage ``` ## **오류 패턴 및 해결책** ### **"Identifier already exists" 오류** - **원인**: REDI 시스템에서 동일한 식별자의 서비스가 중복 등록 - **해결**: 전역 인스턴스 관리자 패턴으로 단일 인스턴스 보장 ### **컴포넌트 재마운트 시 중복 초기화** - **원인**: useEffect가 매번 새로운 초기화 시도 - **해결**: 기존 전역 인스턴스 존재 확인 후 재사용 ### **HMR 환경에서 상태 불안정** - **원인**: 모듈 재로드 시 전역 상태 초기화 - **해결**: 브라우저 캐시 삭제 + window 객체 기반 상태 추적 ## **성능 최적화** ### **초기화 대기 로직** ```typescript // ✅ DO: Promise 기반 비동기 대기 while (globalInitializing) { await new Promise((resolve) => setTimeout(resolve, 50)); } // ❌ DON'T: 동기 대기나 긴 interval setInterval(() => { /* 체크 로직 */ }, 1000); // 너무 느림 ``` ### **메모리 누수 방지** ```typescript // ✅ DO: 컴포넌트 언마운트 시 상태만 정리 useEffect(() => { return () => { setIsInitialized(false); // 전역 인스턴스는 앱 종료 시에만 정리 }; }, []); ``` ## **참고사항** - 이 패턴은 Univer CE의 REDI 아키텍처 특성상 필수적임 - 전역 인스턴스 관리로 브라우저 재로드 없이 안정적 운용 가능 - 개발 환경에서 오류 발생 시 반드시 캐시 삭제 후 재시작