- claude/ 브랜치에서 누락된 .docs 폴더 복원 완료 - dispatch-async 관련 문서 9개 파일 복원 * 01-problem.md, 02-solution-dispatch-helper.md * 03-solution-async-utils.md, 04-solution-queue-system.md * 05-usage-patterns.md, 06-setup-guide.md * 07-changelog.md, 08-troubleshooting.md, README.md - MediaPlayer.v2 관련 문서 4개 파일 복원 * MediaPlayer-v2-README.md, MediaPlayer-v2-Required-Changes.md * MediaPlayer-v2-Risk-Analysis.md, PR-MediaPlayer-v2.md - 기타 분석 문서 2개 파일 복원 * modal-transition-analysis.md, video-player-analysis-and-optimization-plan.md - .gitignore에서 .docs 항목 제거로 문서 추적 가능하도록 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: GLM 4.6 <noreply@z.ai>
607 lines
13 KiB
Markdown
607 lines
13 KiB
Markdown
# 트러블슈팅 가이드
|
|
|
|
## 📋 목차
|
|
|
|
1. [일반적인 문제](#일반적인-문제)
|
|
2. [큐 시스템 문제](#큐-시스템-문제)
|
|
3. [API 호출 문제](#api-호출-문제)
|
|
4. [성능 문제](#성능-문제)
|
|
5. [디버깅 팁](#디버깅-팁)
|
|
|
|
---
|
|
|
|
## 일반적인 문제
|
|
|
|
### 문제 1: dispatch 순서가 여전히 보장되지 않음
|
|
|
|
#### 증상
|
|
```javascript
|
|
dispatch(action1());
|
|
dispatch(action2());
|
|
dispatch(action3());
|
|
// 실행 순서: action2 → action3 → action1
|
|
```
|
|
|
|
#### 가능한 원인
|
|
|
|
1. **일반 dispatch와 큐 dispatch 혼용**
|
|
```javascript
|
|
// ❌ 잘못된 사용
|
|
dispatch(pushPanel({ name: 'PANEL_1' })); // 일반
|
|
dispatch(pushPanelQueued({ name: 'PANEL_2' })); // 큐
|
|
```
|
|
|
|
2. **async/await 없이 비동기 처리**
|
|
```javascript
|
|
// ❌ 잘못된 사용
|
|
fetchData(); // Promise를 기다리지 않음
|
|
dispatch(action());
|
|
```
|
|
|
|
3. **헬퍼 함수를 사용하지 않음**
|
|
```javascript
|
|
// ❌ 잘못된 사용
|
|
dispatch(asyncAction1());
|
|
dispatch(asyncAction2()); // asyncAction1이 완료되기 전에 실행
|
|
```
|
|
|
|
#### 해결 방법
|
|
|
|
**방법 1: 큐 시스템 사용** (패널 액션인 경우)
|
|
```javascript
|
|
// ✅ 올바른 사용
|
|
dispatch(enqueueMultiplePanelActions([
|
|
pushPanelQueued({ name: 'PANEL_1' }),
|
|
pushPanelQueued({ name: 'PANEL_2' }),
|
|
pushPanelQueued({ name: 'PANEL_3' })
|
|
]));
|
|
```
|
|
|
|
**방법 2: createSequentialDispatch 사용**
|
|
```javascript
|
|
// ✅ 올바른 사용
|
|
dispatch(createSequentialDispatch([
|
|
action1(),
|
|
action2(),
|
|
action3()
|
|
]));
|
|
```
|
|
|
|
**방법 3: async/await 사용** (Chrome 68+)
|
|
```javascript
|
|
// ✅ 올바른 사용
|
|
export const myAction = () => async (dispatch, getState) => {
|
|
await dispatch(action1());
|
|
await dispatch(action2());
|
|
await dispatch(action3());
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### 문제 2: "Cannot find module" 에러
|
|
|
|
#### 증상
|
|
```
|
|
Error: Cannot find module '../utils/dispatchHelper'
|
|
Error: Cannot find module '../middleware/panelQueueMiddleware'
|
|
```
|
|
|
|
#### 원인
|
|
- 파일이 존재하지 않음
|
|
- import 경로가 잘못됨
|
|
- 빌드가 필요함
|
|
|
|
#### 해결 방법
|
|
|
|
**Step 1: 파일 존재 확인**
|
|
```bash
|
|
# 프로젝트 루트에서 실행
|
|
ls -la com.twin.app.shoptime/src/utils/dispatchHelper.js
|
|
ls -la com.twin.app.shoptime/src/middleware/panelQueueMiddleware.js
|
|
ls -la com.twin.app.shoptime/src/utils/asyncActionUtils.js
|
|
```
|
|
|
|
**Step 2: 최신 코드 pull**
|
|
```bash
|
|
git fetch origin
|
|
git pull origin <branch-name>
|
|
```
|
|
|
|
**Step 3: node_modules 재설치**
|
|
```bash
|
|
cd com.twin.app.shoptime
|
|
rm -rf node_modules package-lock.json
|
|
npm install
|
|
```
|
|
|
|
**Step 4: 빌드 재실행**
|
|
```bash
|
|
npm run build
|
|
# 또는
|
|
npm start
|
|
```
|
|
|
|
---
|
|
|
|
### 문제 3: 타입 에러 (types is not defined)
|
|
|
|
#### 증상
|
|
```
|
|
ReferenceError: types is not defined
|
|
TypeError: Cannot read property 'ENQUEUE_PANEL_ACTION' of undefined
|
|
```
|
|
|
|
#### 원인
|
|
actionTypes.js에 필요한 타입이 정의되지 않음
|
|
|
|
#### 해결 방법
|
|
|
|
**Step 1: actionTypes.js 확인**
|
|
```javascript
|
|
// src/actions/actionTypes.js
|
|
export const types = {
|
|
// ... 기존 타입들 ...
|
|
|
|
// 큐 관련 타입들 (필수!)
|
|
ENQUEUE_PANEL_ACTION: 'ENQUEUE_PANEL_ACTION',
|
|
PROCESS_PANEL_QUEUE: 'PROCESS_PANEL_QUEUE',
|
|
CLEAR_PANEL_QUEUE: 'CLEAR_PANEL_QUEUE',
|
|
SET_QUEUE_PROCESSING: 'SET_QUEUE_PROCESSING',
|
|
|
|
// 비동기 액션 타입들 (필수!)
|
|
ENQUEUE_ASYNC_PANEL_ACTION: 'ENQUEUE_ASYNC_PANEL_ACTION',
|
|
COMPLETE_ASYNC_PANEL_ACTION: 'COMPLETE_ASYNC_PANEL_ACTION',
|
|
FAIL_ASYNC_PANEL_ACTION: 'FAIL_ASYNC_PANEL_ACTION',
|
|
};
|
|
```
|
|
|
|
**Step 2: import 확인**
|
|
```javascript
|
|
import { types } from '../actions/actionTypes';
|
|
```
|
|
|
|
---
|
|
|
|
## 큐 시스템 문제
|
|
|
|
### 문제 4: 큐가 처리되지 않음
|
|
|
|
#### 증상
|
|
```javascript
|
|
dispatch(pushPanelQueued({ name: panel_names.SEARCH_PANEL }));
|
|
// 아무 일도 일어나지 않음
|
|
// 콘솔 로그도 없음
|
|
```
|
|
|
|
#### 원인
|
|
**panelQueueMiddleware가 등록되지 않음** (가장 흔한 문제!)
|
|
|
|
#### 해결 방법
|
|
|
|
**Step 1: store.js 확인**
|
|
```javascript
|
|
// src/store/store.js
|
|
import panelQueueMiddleware from '../middleware/panelQueueMiddleware';
|
|
|
|
export const store = createStore(
|
|
rootReducer,
|
|
applyMiddleware(
|
|
thunk,
|
|
panelHistoryMiddleware,
|
|
autoCloseMiddleware,
|
|
panelQueueMiddleware // ← 이것이 있는지 확인!
|
|
)
|
|
);
|
|
```
|
|
|
|
**Step 2: import 경로 확인**
|
|
```javascript
|
|
// ✅ 올바른 import
|
|
import panelQueueMiddleware from '../middleware/panelQueueMiddleware';
|
|
|
|
// ❌ 잘못된 import
|
|
import { panelQueueMiddleware } from '../middleware/panelQueueMiddleware';
|
|
// default export이므로 중괄호 없이 import해야 함
|
|
```
|
|
|
|
**Step 3: 앱 재시작**
|
|
```bash
|
|
# 개발 서버 재시작
|
|
npm start
|
|
```
|
|
|
|
**Step 4: 브라우저 캐시 삭제**
|
|
- Chrome: Ctrl+Shift+R (Windows) 또는 Cmd+Shift+R (Mac)
|
|
|
|
---
|
|
|
|
### 문제 5: 큐가 무한 루프에 빠짐
|
|
|
|
#### 증상
|
|
```
|
|
[panelQueueMiddleware] 🚀 ACTION_ENQUEUED
|
|
[panelQueueMiddleware] ⚡ STARTING_QUEUE_PROCESS
|
|
[panelReducer] 🟡 PROCESS_PANEL_QUEUE
|
|
[panelReducer] 🟡 PROCESS_PANEL_QUEUE
|
|
[panelReducer] 🟡 PROCESS_PANEL_QUEUE
|
|
... (무한 반복)
|
|
```
|
|
|
|
#### 원인
|
|
1. 큐 처리 중에 다시 큐에 액션 추가
|
|
2. `isProcessingQueue` 플래그가 제대로 설정되지 않음
|
|
|
|
#### 해결 방법
|
|
|
|
**방법 1: 큐 액션 내부에서 일반 dispatch 사용**
|
|
```javascript
|
|
// ❌ 잘못된 사용 (무한 루프 발생)
|
|
export const myAction = () => (dispatch) => {
|
|
dispatch(pushPanelQueued({ name: 'PANEL_1' }));
|
|
dispatch(pushPanelQueued({ name: 'PANEL_2' })); // 큐 처리 중 큐 추가
|
|
};
|
|
|
|
// ✅ 올바른 사용
|
|
export const myAction = () => (dispatch) => {
|
|
dispatch(enqueueMultiplePanelActions([
|
|
pushPanelQueued({ name: 'PANEL_1' }),
|
|
pushPanelQueued({ name: 'PANEL_2' })
|
|
]));
|
|
};
|
|
```
|
|
|
|
**방법 2: 리듀서 로직 확인**
|
|
```javascript
|
|
// panelReducer.js에서 확인
|
|
case types.PROCESS_PANEL_QUEUE: {
|
|
// 이미 처리 중이면 무시
|
|
if (state.isProcessingQueue || state.panelActionQueue.length === 0) {
|
|
return state; // ← 이 로직이 있는지 확인
|
|
}
|
|
// ...
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 문제 6: 큐 통계가 업데이트되지 않음
|
|
|
|
#### 증상
|
|
```javascript
|
|
store.getState().panels.queueStats
|
|
// { totalProcessed: 0, failedCount: 0, averageProcessingTime: 0 }
|
|
// 항상 0으로 유지됨
|
|
```
|
|
|
|
#### 원인
|
|
큐 처리가 정상적으로 완료되지 않음
|
|
|
|
#### 해결 방법
|
|
|
|
**Step 1: 콘솔 로그 확인**
|
|
```
|
|
[panelReducer] ✅ QUEUE_ITEM_PROCESSED ← 이 로그가 보이는지 확인
|
|
```
|
|
|
|
**Step 2: 에러 발생 확인**
|
|
```javascript
|
|
store.getState().panels.queueError
|
|
// null이어야 정상
|
|
```
|
|
|
|
**Step 3: 큐 처리 완료 여부 확인**
|
|
```javascript
|
|
store.getState().panels.isProcessingQueue
|
|
// false여야 정상 (처리 완료)
|
|
```
|
|
|
|
---
|
|
|
|
## API 호출 문제
|
|
|
|
### 문제 7: API 성공인데 onFail이 호출됨
|
|
|
|
#### 증상
|
|
```javascript
|
|
// API 호출
|
|
// HTTP 200, retCode: 0
|
|
// 그런데 onFail이 호출됨
|
|
```
|
|
|
|
#### 원인
|
|
프로젝트 성공 기준을 이해하지 못함
|
|
|
|
#### 프로젝트 성공 기준
|
|
**HTTP 200-299 + retCode 0/'0' 둘 다 만족해야 성공!**
|
|
|
|
```javascript
|
|
// ✅ 성공 케이스
|
|
{ status: 200, data: { retCode: 0, data: {...} } }
|
|
{ status: 200, data: { retCode: '0', data: {...} } }
|
|
|
|
// ❌ 실패 케이스
|
|
{ status: 200, data: { retCode: 1, message: '에러' } } // retCode가 0이 아님
|
|
{ status: 500, data: { retCode: 0, data: {...} } } // HTTP 에러
|
|
```
|
|
|
|
#### 해결 방법
|
|
|
|
**방법 1: isApiSuccess 사용**
|
|
```javascript
|
|
import { isApiSuccess } from '../utils/asyncActionUtils';
|
|
|
|
const response = { status: 200 };
|
|
const responseData = { retCode: 1, message: '에러' };
|
|
|
|
if (isApiSuccess(response, responseData)) {
|
|
// 성공 처리
|
|
} else {
|
|
// 실패 처리 (retCode가 1이므로 실패!)
|
|
}
|
|
```
|
|
|
|
**방법 2: asyncActionUtils 사용**
|
|
```javascript
|
|
import { tAxiosToPromise } from '../utils/asyncActionUtils';
|
|
|
|
const result = await tAxiosToPromise(...);
|
|
|
|
if (result.success) {
|
|
// HTTP 200-299 + retCode 0/'0'
|
|
console.log(result.data);
|
|
} else {
|
|
// 실패
|
|
console.error(result.error);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 문제 8: API 타임아웃이 작동하지 않음
|
|
|
|
#### 증상
|
|
```javascript
|
|
dispatch(enqueueAsyncPanelAction({
|
|
asyncAction: (d, gs, onS, onF) => { /* 느린 API */ },
|
|
timeout: 5000 // 5초
|
|
}));
|
|
// 10초가 지나도 타임아웃되지 않음
|
|
```
|
|
|
|
#### 원인
|
|
1. `withTimeout`이 적용되지 않음
|
|
2. 타임아웃 값이 잘못 설정됨
|
|
|
|
#### 해결 방법
|
|
|
|
**방법 1: enqueueAsyncPanelAction 사용 시**
|
|
```javascript
|
|
// ✅ timeout 옵션 사용
|
|
dispatch(enqueueAsyncPanelAction({
|
|
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
|
TAxios(dispatch, getState, 'get', URL, {}, {}, onSuccess, onFail);
|
|
},
|
|
timeout: 5000, // 5초 (ms 단위)
|
|
onFail: (error) => {
|
|
if (error.code === 'TIMEOUT') {
|
|
console.error('타임아웃 발생!');
|
|
}
|
|
}
|
|
}));
|
|
```
|
|
|
|
**방법 2: withTimeout 직접 사용**
|
|
```javascript
|
|
import { withTimeout, fetchApi } from '../utils/asyncActionUtils';
|
|
|
|
const result = await withTimeout(
|
|
fetchApi('/api/slow-endpoint'),
|
|
5000, // 5초
|
|
'요청 시간이 초과되었습니다'
|
|
);
|
|
|
|
if (result.error?.code === 'TIMEOUT') {
|
|
console.error('타임아웃!');
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 성능 문제
|
|
|
|
### 문제 9: 큐 처리가 너무 느림
|
|
|
|
#### 증상
|
|
```javascript
|
|
// 100개의 패널 액션을 큐에 추가
|
|
// 처리하는데 10초 이상 소요
|
|
```
|
|
|
|
#### 원인
|
|
1. 각 액션이 복잡한 로직 수행
|
|
2. 동기적으로 처리되어 병목 발생
|
|
|
|
#### 해결 방법
|
|
|
|
**방법 1: 불필요한 액션 제거**
|
|
```javascript
|
|
// ❌ 잘못된 사용
|
|
for (let i = 0; i < 100; i++) {
|
|
dispatch(pushPanelQueued({ name: `PANEL_${i}` }));
|
|
}
|
|
|
|
// ✅ 올바른 사용 - 필요한 것만
|
|
dispatch(pushPanelQueued({ name: 'MAIN_PANEL' }));
|
|
```
|
|
|
|
**방법 2: 배치 처리**
|
|
```javascript
|
|
// 한 번에 여러 액션 추가
|
|
dispatch(enqueueMultiplePanelActions(
|
|
panels.map(panel => pushPanelQueued(panel))
|
|
));
|
|
```
|
|
|
|
**방법 3: 병렬 처리가 필요하면 큐 사용 안함**
|
|
```javascript
|
|
// 순서가 중요하지 않은 경우
|
|
dispatch(createParallelDispatch([
|
|
fetchData1(),
|
|
fetchData2(),
|
|
fetchData3()
|
|
]));
|
|
```
|
|
|
|
---
|
|
|
|
### 문제 10: 메모리 누수
|
|
|
|
#### 증상
|
|
```javascript
|
|
// 오랜 시간 앱 사용 후
|
|
store.getState().panels.completedAsyncActions.length
|
|
// → 10000개 이상
|
|
```
|
|
|
|
#### 원인
|
|
완료된 비동기 액션 ID가 계속 누적됨
|
|
|
|
#### 해결 방법
|
|
|
|
**방법 1: 주기적으로 클리어**
|
|
```javascript
|
|
// 일정 시간마다 완료된 액션 정리
|
|
setInterval(() => {
|
|
const state = store.getState().panels;
|
|
|
|
if (state.completedAsyncActions.length > 1000) {
|
|
// 클리어 액션 dispatch
|
|
dispatch({ type: types.CLEAR_COMPLETED_ASYNC_ACTIONS });
|
|
}
|
|
}, 60000); // 1분마다
|
|
```
|
|
|
|
**방법 2: 리듀서에 최대 개수 제한 추가**
|
|
```javascript
|
|
// panelReducer.js
|
|
case types.COMPLETE_ASYNC_PANEL_ACTION: {
|
|
const newCompleted = [...state.completedAsyncActions, action.payload.actionId];
|
|
|
|
// 최근 100개만 유지
|
|
const trimmed = newCompleted.slice(-100);
|
|
|
|
return {
|
|
...state,
|
|
completedAsyncActions: trimmed
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 디버깅 팁
|
|
|
|
### Tip 1: 콘솔 로그 활용
|
|
|
|
모든 헬퍼 함수와 미들웨어는 상세한 로그를 출력합니다:
|
|
|
|
```javascript
|
|
// 큐 관련 로그
|
|
[panelQueueMiddleware] 🚀 ACTION_ENQUEUED
|
|
[panelQueueMiddleware] ⚡ STARTING_QUEUE_PROCESS
|
|
[panelReducer] 🟡 PROCESS_PANEL_QUEUE
|
|
[panelReducer] 🟡 PROCESSING_QUEUE_ITEM
|
|
[panelReducer] ✅ QUEUE_ITEM_PROCESSED
|
|
|
|
// 비동기 액션 로그
|
|
[queuedPanelActions] 🔄 ENQUEUE_ASYNC_PANEL_ACTION
|
|
[queuedPanelActions] ⚡ EXECUTING_ASYNC_ACTION
|
|
[queuedPanelActions] ✅ ASYNC_ACTION_SUCCESS
|
|
|
|
// asyncActionUtils 로그
|
|
[asyncActionUtils] 🌐 FETCH_API_START
|
|
[asyncActionUtils] 📊 API_RESPONSE
|
|
[asyncActionUtils] ✅ TAXIOS_SUCCESS
|
|
```
|
|
|
|
### Tip 2: Redux DevTools 사용
|
|
|
|
1. Chrome 확장 프로그램 설치: Redux DevTools
|
|
2. 개발자 도구 → Redux 탭
|
|
3. 액션 히스토리 확인
|
|
4. State diff 확인
|
|
|
|
### Tip 3: 브레이크포인트 설정
|
|
|
|
```javascript
|
|
// 디버깅용 브레이크포인트
|
|
export const myAction = () => (dispatch, getState) => {
|
|
debugger; // ← 여기서 멈춤
|
|
|
|
const state = getState();
|
|
console.log('Current state:', state);
|
|
|
|
dispatch(action1());
|
|
|
|
debugger; // ← 여기서 다시 멈춤
|
|
};
|
|
```
|
|
|
|
### Tip 4: State 스냅샷
|
|
|
|
```javascript
|
|
// 콘솔에서 실행
|
|
const snapshot = JSON.parse(JSON.stringify(store.getState()));
|
|
console.log(snapshot);
|
|
|
|
// 특정 부분만
|
|
const panelsSnapshot = JSON.parse(JSON.stringify(store.getState().panels));
|
|
console.log(panelsSnapshot);
|
|
```
|
|
|
|
### Tip 5: 큐 상태 모니터링
|
|
|
|
```javascript
|
|
// 콘솔에서 실행
|
|
window.monitorQueue = setInterval(() => {
|
|
const state = store.getState().panels;
|
|
console.log('Queue status:', {
|
|
queueLength: state.panelActionQueue.length,
|
|
isProcessing: state.isProcessingQueue,
|
|
stats: state.queueStats
|
|
});
|
|
}, 1000);
|
|
|
|
// 중지
|
|
clearInterval(window.monitorQueue);
|
|
```
|
|
|
|
---
|
|
|
|
## 도움이 필요하신가요?
|
|
|
|
### 체크리스트
|
|
|
|
문제 해결 전에 다음을 확인하세요:
|
|
|
|
- [ ] panelQueueMiddleware가 store.js에 등록되어 있는가?
|
|
- [ ] 필요한 파일들이 모두 존재하는가?
|
|
- [ ] actionTypes.js에 필요한 타입들이 정의되어 있는가?
|
|
- [ ] 콘솔 로그를 확인했는가?
|
|
- [ ] Redux DevTools로 액션 흐름을 확인했는가?
|
|
- [ ] 앱을 재시작했는가?
|
|
- [ ] 브라우저 캐시를 삭제했는가?
|
|
|
|
### 추가 리소스
|
|
|
|
- [README.md](./README.md) - 전체 개요
|
|
- [06-setup-guide.md](./06-setup-guide.md) - 설정 가이드
|
|
- [05-usage-patterns.md](./05-usage-patterns.md) - 사용 패턴
|
|
- [07-changelog.md](./07-changelog.md) - 변경 이력
|
|
|
|
---
|
|
|
|
**작성일**: 2025-11-10
|
|
**최종 수정일**: 2025-11-10
|