327 lines
9.0 KiB
Plaintext
327 lines
9.0 KiB
Plaintext
---
|
|
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<Univer> {
|
|
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<Univer | null>(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 아키텍처 특성상 필수적임
|
|
- 전역 인스턴스 관리로 브라우저 재로드 없이 안정적 운용 가능
|
|
- 개발 환경에서 오류 발생 시 반드시 캐시 삭제 후 재시작
|