[문서화] Dispatch 비동기 처리 순서 보장 솔루션 문서 작성
dispatch 비동기 처리 순서 보장 문제와 해결 방법을 체계적으로 정리한 문서를 작성했습니다. ## 작성된 문서 1. README.md - 전체 개요 및 목차 2. 01-problem.md - 문제 상황 및 원인 분석 3. 02-solution-dispatch-helper.md - dispatchHelper.js 솔루션 4. 03-solution-async-utils.md - asyncActionUtils.js 솔루션 5. 04-solution-queue-system.md - 큐 기반 패널 액션 시스템 6. 05-usage-patterns.md - 사용 패턴 및 실전 예제 ## 주요 내용 ### 문제 - Redux-thunk에서 여러 dispatch 순서가 보장되지 않는 문제 - setTimeout(fn, 0) 임시방편의 한계 ### 해결 방법 1. **dispatchHelper.js** (2025-11-05) - createSequentialDispatch: Promise 체인 기반 순차 실행 - createApiThunkWithChain: API 후 dispatch 체이닝 - withLoadingState: 로딩 상태 자동 관리 2. **asyncActionUtils.js** (2025-11-06) - 성공 기준 명확화: HTTP 200-299 + retCode 0/'0' - reject 없이 resolve만 사용하여 Promise 체인 보장 - 타임아웃 지원 3. **큐 기반 패널 액션 시스템** (2025-11-06) - queuedPanelActions.js: 패널 액션 큐 - panelQueueMiddleware.js: 자동 큐 처리 - 비동기 액션 순차 실행 ## 관련 커밋 -9490d72[251105] feat: dispatchHelper.js -5bd2774[251106] feat: Queued Panel functions -f9290a1[251106] fix: Dispatch Queue implementation
This commit is contained in:
626
.docs/dispatch-async/04-solution-queue-system.md
Normal file
626
.docs/dispatch-async/04-solution-queue-system.md
Normal file
@@ -0,0 +1,626 @@
|
||||
# 해결 방법 3: 큐 기반 패널 액션 시스템
|
||||
|
||||
## 📦 개요
|
||||
|
||||
**관련 파일**:
|
||||
- `src/actions/queuedPanelActions.js`
|
||||
- `src/middleware/panelQueueMiddleware.js`
|
||||
- `src/reducers/panelReducer.js`
|
||||
|
||||
**작성일**: 2025-11-06
|
||||
**커밋**:
|
||||
- `5bd2774 [251106] feat: Queued Panel functions`
|
||||
- `f9290a1 [251106] fix: Dispatch Queue implementation`
|
||||
|
||||
미들웨어 기반의 **액션 큐 처리 시스템**으로, 패널 액션들을 순차적으로 실행합니다.
|
||||
|
||||
## 🎯 핵심 개념
|
||||
|
||||
### 왜 큐 시스템이 필요한가?
|
||||
|
||||
패널 관련 액션들은 특히 순서가 중요합니다:
|
||||
|
||||
```javascript
|
||||
// 문제 상황
|
||||
dispatch(pushPanel({ name: 'SEARCH' })); // 검색 패널 열기
|
||||
dispatch(updatePanel({ results: [...] })); // 검색 결과 업데이트
|
||||
dispatch(popPanel('LOADING')); // 로딩 패널 닫기
|
||||
|
||||
// 실제 실행 순서 (문제!)
|
||||
// → popPanel이 먼저 실행될 수 있음
|
||||
// → updatePanel이 pushPanel보다 먼저 실행될 수 있음
|
||||
```
|
||||
|
||||
### 큐 시스템의 동작 방식
|
||||
|
||||
```
|
||||
[큐에 추가] → [미들웨어 감지] → [순차 처리] → [완료]
|
||||
↓ ↓ ↓ ↓
|
||||
ENQUEUE 자동 감지 시작 PROCESS_QUEUE 다음 액션
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔑 주요 컴포넌트
|
||||
|
||||
### 1. queuedPanelActions.js
|
||||
|
||||
패널 액션을 큐에 추가하는 액션 크리에이터들
|
||||
|
||||
### 2. panelQueueMiddleware.js
|
||||
|
||||
큐에 액션이 추가되면 자동으로 처리를 시작하는 미들웨어
|
||||
|
||||
### 3. panelReducer.js
|
||||
|
||||
큐 상태를 관리하는 리듀서
|
||||
|
||||
---
|
||||
|
||||
## 📋 기본 패널 액션
|
||||
|
||||
### 1. pushPanelQueued
|
||||
|
||||
패널을 큐에 추가하여 순차적으로 열기
|
||||
|
||||
```javascript
|
||||
import { pushPanelQueued } from '../actions/queuedPanelActions';
|
||||
|
||||
// 기본 사용
|
||||
dispatch(pushPanelQueued(
|
||||
{ name: panel_names.SEARCH_PANEL },
|
||||
false // duplicatable
|
||||
));
|
||||
|
||||
// 중복 허용
|
||||
dispatch(pushPanelQueued(
|
||||
{ name: panel_names.PRODUCT_DETAIL, productId: 123 },
|
||||
true // 중복 허용
|
||||
));
|
||||
```
|
||||
|
||||
### 2. popPanelQueued
|
||||
|
||||
패널을 큐를 통해 제거
|
||||
|
||||
```javascript
|
||||
import { popPanelQueued } from '../actions/queuedPanelActions';
|
||||
|
||||
// 마지막 패널 제거
|
||||
dispatch(popPanelQueued());
|
||||
|
||||
// 특정 패널 제거
|
||||
dispatch(popPanelQueued(panel_names.SEARCH_PANEL));
|
||||
```
|
||||
|
||||
### 3. updatePanelQueued
|
||||
|
||||
패널 정보를 큐를 통해 업데이트
|
||||
|
||||
```javascript
|
||||
import { updatePanelQueued } from '../actions/queuedPanelActions';
|
||||
|
||||
dispatch(updatePanelQueued({
|
||||
name: panel_names.SEARCH_PANEL,
|
||||
panelInfo: {
|
||||
results: [...],
|
||||
totalCount: 100
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
||||
### 4. resetPanelsQueued
|
||||
|
||||
모든 패널을 초기화
|
||||
|
||||
```javascript
|
||||
import { resetPanelsQueued } from '../actions/queuedPanelActions';
|
||||
|
||||
// 빈 패널로 초기화
|
||||
dispatch(resetPanelsQueued());
|
||||
|
||||
// 특정 패널들로 초기화
|
||||
dispatch(resetPanelsQueued([
|
||||
{ name: panel_names.HOME }
|
||||
]));
|
||||
```
|
||||
|
||||
### 5. enqueueMultiplePanelActions
|
||||
|
||||
여러 패널 액션을 한 번에 큐에 추가
|
||||
|
||||
```javascript
|
||||
import { enqueueMultiplePanelActions, pushPanelQueued, updatePanelQueued, popPanelQueued }
|
||||
from '../actions/queuedPanelActions';
|
||||
|
||||
dispatch(enqueueMultiplePanelActions([
|
||||
pushPanelQueued({ name: panel_names.SEARCH_PANEL }),
|
||||
updatePanelQueued({ name: panel_names.SEARCH_PANEL, panelInfo: { query: 'test' } }),
|
||||
popPanelQueued(panel_names.LOADING_PANEL)
|
||||
]));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 비동기 패널 액션
|
||||
|
||||
### 1. enqueueAsyncPanelAction
|
||||
|
||||
비동기 작업(API 호출 등)을 큐에 추가하여 순차 실행
|
||||
|
||||
**파일**: `src/actions/queuedPanelActions.js:173-199`
|
||||
|
||||
```javascript
|
||||
import { enqueueAsyncPanelAction } from '../actions/queuedPanelActions';
|
||||
|
||||
dispatch(enqueueAsyncPanelAction({
|
||||
id: 'search_products_123', // 고유 ID
|
||||
|
||||
// 비동기 액션 (TAxios 등)
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
'post',
|
||||
URLS.SEARCH_PRODUCTS,
|
||||
{},
|
||||
{ keyword: 'test' },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
},
|
||||
|
||||
// 성공 콜백
|
||||
onSuccess: (response) => {
|
||||
console.log('검색 성공:', response);
|
||||
dispatch(pushPanelQueued({
|
||||
name: panel_names.SEARCH_RESULT,
|
||||
results: response.data.results
|
||||
}));
|
||||
},
|
||||
|
||||
// 실패 콜백
|
||||
onFail: (error) => {
|
||||
console.error('검색 실패:', error);
|
||||
dispatch(pushPanelQueued({
|
||||
name: panel_names.ERROR,
|
||||
message: error.message
|
||||
}));
|
||||
},
|
||||
|
||||
// 완료 콜백 (성공/실패 모두 호출)
|
||||
onFinish: (isSuccess, result) => {
|
||||
console.log('검색 완료:', isSuccess ? '성공' : '실패');
|
||||
},
|
||||
|
||||
// 타임아웃 (ms)
|
||||
timeout: 10000 // 10초
|
||||
}));
|
||||
```
|
||||
|
||||
### 동작 흐름
|
||||
|
||||
```
|
||||
1. enqueueAsyncPanelAction 호출
|
||||
↓
|
||||
2. ENQUEUE_ASYNC_PANEL_ACTION dispatch
|
||||
↓
|
||||
3. executeAsyncAction 자동 실행
|
||||
↓
|
||||
4. wrapAsyncAction으로 Promise 래핑
|
||||
↓
|
||||
5. withTimeout으로 타임아웃 적용
|
||||
↓
|
||||
6. 결과에 따라 onSuccess 또는 onFail 호출
|
||||
↓
|
||||
7. COMPLETE_ASYNC_PANEL_ACTION 또는 FAIL_ASYNC_PANEL_ACTION dispatch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 API 호출 후 패널 액션
|
||||
|
||||
### createApiWithPanelActions
|
||||
|
||||
API 호출 후 여러 패널 액션을 자동으로 실행
|
||||
|
||||
**파일**: `src/actions/queuedPanelActions.js:355-394`
|
||||
|
||||
```javascript
|
||||
import { createApiWithPanelActions } from '../actions/queuedPanelActions';
|
||||
|
||||
dispatch(createApiWithPanelActions({
|
||||
// API 호출
|
||||
apiCall: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
'post',
|
||||
URLS.SEARCH_PRODUCTS,
|
||||
{},
|
||||
{ keyword: 'laptop' },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
},
|
||||
|
||||
// API 성공 후 실행할 패널 액션들
|
||||
panelActions: [
|
||||
// Plain action
|
||||
{ type: 'PUSH_PANEL', payload: { name: panel_names.SEARCH_PANEL } },
|
||||
|
||||
// Dynamic action (response 사용)
|
||||
(response) => updatePanelQueued({
|
||||
name: panel_names.SEARCH_PANEL,
|
||||
panelInfo: {
|
||||
results: response.data.results,
|
||||
totalCount: response.data.totalCount
|
||||
}
|
||||
}),
|
||||
|
||||
// 또 다른 패널 액션
|
||||
popPanelQueued(panel_names.LOADING_PANEL)
|
||||
],
|
||||
|
||||
// API 성공 콜백
|
||||
onApiSuccess: (response) => {
|
||||
console.log('API 성공:', response.data.totalCount, '개 검색됨');
|
||||
},
|
||||
|
||||
// API 실패 콜백
|
||||
onApiFail: (error) => {
|
||||
console.error('API 실패:', error);
|
||||
dispatch(pushPanelQueued({
|
||||
name: panel_names.ERROR,
|
||||
message: '검색에 실패했습니다'
|
||||
}));
|
||||
}
|
||||
}));
|
||||
```
|
||||
|
||||
### 사용 예제: 상품 검색
|
||||
|
||||
```javascript
|
||||
export const searchProducts = (keyword) =>
|
||||
createApiWithPanelActions({
|
||||
apiCall: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
'post',
|
||||
URLS.SEARCH_PRODUCTS,
|
||||
{},
|
||||
{ keyword },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
},
|
||||
panelActions: [
|
||||
// 1. 로딩 패널 닫기
|
||||
popPanelQueued(panel_names.LOADING_PANEL),
|
||||
|
||||
// 2. 검색 결과 패널 열기
|
||||
(response) => pushPanelQueued({
|
||||
name: panel_names.SEARCH_RESULT,
|
||||
results: response.data.results
|
||||
}),
|
||||
|
||||
// 3. 검색 히스토리 업데이트
|
||||
(response) => updatePanelQueued({
|
||||
name: panel_names.SEARCH_HISTORY,
|
||||
panelInfo: { lastSearch: keyword }
|
||||
})
|
||||
],
|
||||
onApiSuccess: (response) => {
|
||||
console.log(`${response.data.totalCount}개의 상품을 찾았습니다`);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 비동기 액션 시퀀스
|
||||
|
||||
### createAsyncPanelSequence
|
||||
|
||||
여러 비동기 액션을 **순차적으로** 실행
|
||||
|
||||
**파일**: `src/actions/queuedPanelActions.js:401-445`
|
||||
|
||||
```javascript
|
||||
import { createAsyncPanelSequence } from '../actions/queuedPanelActions';
|
||||
|
||||
dispatch(createAsyncPanelSequence([
|
||||
// 첫 번째 비동기 액션
|
||||
{
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_USER_INFO, {}, {}, onSuccess, onFail);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
console.log('사용자 정보 조회 성공');
|
||||
dispatch(pushPanelQueued({
|
||||
name: panel_names.USER_INFO,
|
||||
userInfo: response.data.data
|
||||
}));
|
||||
},
|
||||
onFail: (error) => {
|
||||
console.error('사용자 정보 조회 실패:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 두 번째 비동기 액션 (첫 번째 완료 후 실행)
|
||||
{
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
const userInfo = getState().user.info;
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
'get',
|
||||
URLS.GET_CART,
|
||||
{},
|
||||
{ mbrNo: userInfo.mbrNo },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
console.log('카트 정보 조회 성공');
|
||||
dispatch(updatePanelQueued({
|
||||
name: panel_names.USER_INFO,
|
||||
panelInfo: { cartCount: response.data.data.length }
|
||||
}));
|
||||
},
|
||||
onFail: (error) => {
|
||||
console.error('카트 정보 조회 실패:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 세 번째 비동기 액션 (두 번째 완료 후 실행)
|
||||
{
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(dispatch, getState, 'get', URLS.GET_ORDERS, {}, {}, onSuccess, onFail);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
console.log('주문 정보 조회 성공');
|
||||
dispatch(pushPanelQueued({
|
||||
name: panel_names.ORDER_LIST,
|
||||
orders: response.data.data
|
||||
}));
|
||||
},
|
||||
onFail: (error) => {
|
||||
console.error('주문 정보 조회 실패:', error);
|
||||
// 실패 시 시퀀스 중단
|
||||
}
|
||||
}
|
||||
]));
|
||||
```
|
||||
|
||||
### 동작 흐름
|
||||
|
||||
```
|
||||
Action 1 실행 → 성공? → Action 2 실행 → 성공? → Action 3 실행
|
||||
↓ ↓
|
||||
실패 시 실패 시
|
||||
중단 중단
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 미들웨어: panelQueueMiddleware
|
||||
|
||||
### 동작 원리
|
||||
|
||||
**파일**: `src/middleware/panelQueueMiddleware.js`
|
||||
|
||||
```javascript
|
||||
const panelQueueMiddleware = (store) => (next) => (action) => {
|
||||
const result = next(action);
|
||||
|
||||
// 큐에 액션이 추가되면 자동으로 처리 시작
|
||||
if (action.type === types.ENQUEUE_PANEL_ACTION) {
|
||||
console.log('[panelQueueMiddleware] 🚀 ACTION_ENQUEUED', {
|
||||
action: action.payload.action,
|
||||
queueId: action.payload.id,
|
||||
});
|
||||
|
||||
// setTimeout을 사용하여 현재 액션이 완전히 처리된 후에 큐 처리 시작
|
||||
setTimeout(() => {
|
||||
const currentState = store.getState();
|
||||
|
||||
if (currentState.panels) {
|
||||
// 이미 처리 중이 아니고 큐에 액션이 있으면 처리 시작
|
||||
if (!currentState.panels.isProcessingQueue &&
|
||||
currentState.panels.panelActionQueue.length > 0) {
|
||||
console.log('[panelQueueMiddleware] ⚡ STARTING_QUEUE_PROCESS');
|
||||
store.dispatch({ type: types.PROCESS_PANEL_QUEUE });
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// 큐 처리가 완료되고 남은 큐가 있으면 계속 처리
|
||||
if (action.type === types.PROCESS_PANEL_QUEUE) {
|
||||
setTimeout(() => {
|
||||
const currentState = store.getState();
|
||||
|
||||
if (currentState.panels) {
|
||||
// 처리 중이 아니고 큐에 남은 액션이 있으면 계속 처리
|
||||
if (!currentState.panels.isProcessingQueue &&
|
||||
currentState.panels.panelActionQueue.length > 0) {
|
||||
console.log('[panelQueueMiddleware] 🔄 CONTINUING_QUEUE_PROCESS');
|
||||
store.dispatch({ type: types.PROCESS_PANEL_QUEUE });
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
```
|
||||
|
||||
### 주요 특징
|
||||
|
||||
1. ✅ **자동 시작**: 큐에 액션 추가 시 자동으로 처리 시작
|
||||
2. ✅ **연속 처리**: 한 액션 완료 후 자동으로 다음 액션 처리
|
||||
3. ✅ **중복 방지**: 이미 처리 중이면 새로 시작하지 않음
|
||||
4. ✅ **로깅**: 모든 단계에서 로그 출력
|
||||
|
||||
---
|
||||
|
||||
## 📊 리듀서 상태 구조
|
||||
|
||||
### panelReducer.js의 큐 관련 상태
|
||||
|
||||
```javascript
|
||||
{
|
||||
panels: [], // 실제 패널 스택
|
||||
lastPanelAction: 'push', // 마지막 액션 타입
|
||||
|
||||
// 큐 관련 상태
|
||||
panelActionQueue: [ // 처리 대기 중인 큐
|
||||
{
|
||||
id: 'queue_item_1_1699999999999',
|
||||
action: 'PUSH_PANEL',
|
||||
panel: { name: 'SEARCH_PANEL' },
|
||||
duplicatable: false,
|
||||
timestamp: 1699999999999
|
||||
},
|
||||
// ...
|
||||
],
|
||||
|
||||
isProcessingQueue: false, // 큐 처리 중 여부
|
||||
queueError: null, // 큐 처리 에러
|
||||
|
||||
queueStats: { // 큐 통계
|
||||
totalProcessed: 0, // 총 처리된 액션 수
|
||||
failedCount: 0, // 실패한 액션 수
|
||||
averageProcessingTime: 0 // 평균 처리 시간 (ms)
|
||||
},
|
||||
|
||||
// 비동기 액션 상태
|
||||
asyncActions: { // 실행 중인 비동기 액션들
|
||||
'async_action_1': {
|
||||
id: 'async_action_1',
|
||||
status: 'pending', // 'pending' | 'success' | 'failed'
|
||||
timestamp: 1699999999999
|
||||
}
|
||||
},
|
||||
|
||||
completedAsyncActions: [ // 완료된 액션 ID들
|
||||
'async_action_1',
|
||||
'async_action_2'
|
||||
],
|
||||
|
||||
failedAsyncActions: [ // 실패한 액션 ID들
|
||||
'async_action_3'
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 실제 사용 시나리오
|
||||
|
||||
### 시나리오 1: 검색 플로우
|
||||
|
||||
```javascript
|
||||
export const performSearch = (keyword) => (dispatch) => {
|
||||
// 1. 로딩 패널 열기
|
||||
dispatch(pushPanelQueued({ name: panel_names.LOADING }));
|
||||
|
||||
// 2. 검색 API 호출 후 결과 표시
|
||||
dispatch(createApiWithPanelActions({
|
||||
apiCall: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(dispatch, getState, 'post', URLS.SEARCH, {}, { keyword }, onSuccess, onFail);
|
||||
},
|
||||
panelActions: [
|
||||
popPanelQueued(panel_names.LOADING),
|
||||
(response) => pushPanelQueued({
|
||||
name: panel_names.SEARCH_RESULT,
|
||||
results: response.data.results
|
||||
})
|
||||
]
|
||||
}));
|
||||
};
|
||||
```
|
||||
|
||||
### 시나리오 2: 다단계 결제 프로세스
|
||||
|
||||
```javascript
|
||||
export const processCheckout = (orderInfo) =>
|
||||
createAsyncPanelSequence([
|
||||
// 1단계: 주문 검증
|
||||
{
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(dispatch, getState, 'post', URLS.VALIDATE_ORDER, {}, orderInfo, onSuccess, onFail);
|
||||
},
|
||||
onSuccess: () => {
|
||||
dispatch(updatePanelQueued({
|
||||
name: panel_names.CHECKOUT,
|
||||
panelInfo: { step: 1, status: 'validated' }
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 2단계: 결제 처리
|
||||
{
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
TAxios(dispatch, getState, 'post', URLS.PROCESS_PAYMENT, {}, orderInfo, onSuccess, onFail);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
dispatch(updatePanelQueued({
|
||||
name: panel_names.CHECKOUT,
|
||||
panelInfo: { step: 2, paymentId: response.data.data.paymentId }
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 3단계: 주문 확정
|
||||
{
|
||||
asyncAction: (dispatch, getState, onSuccess, onFail) => {
|
||||
const state = getState();
|
||||
const paymentId = state.panels.panels.find(p => p.name === panel_names.CHECKOUT)
|
||||
.panelInfo.paymentId;
|
||||
|
||||
TAxios(
|
||||
dispatch,
|
||||
getState,
|
||||
'post',
|
||||
URLS.CONFIRM_ORDER,
|
||||
{},
|
||||
{ ...orderInfo, paymentId },
|
||||
onSuccess,
|
||||
onFail
|
||||
);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
dispatch(popPanelQueued(panel_names.CHECKOUT));
|
||||
dispatch(pushPanelQueued({
|
||||
name: panel_names.ORDER_COMPLETE,
|
||||
orderId: response.data.data.orderId
|
||||
}));
|
||||
}
|
||||
}
|
||||
]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 장점
|
||||
|
||||
1. **완벽한 순서 보장**: 큐 시스템으로 100% 순서 보장
|
||||
2. **자동 처리**: 미들웨어가 자동으로 큐 처리
|
||||
3. **비동기 지원**: API 호출 등 비동기 작업 완벽 지원
|
||||
4. **타임아웃**: 응답 없는 작업 자동 처리
|
||||
5. **에러 복구**: 에러 발생 시에도 다음 액션 계속 처리
|
||||
6. **통계**: 큐 처리 통계 자동 수집
|
||||
|
||||
## ⚠️ 주의사항
|
||||
|
||||
1. **미들웨어 등록**: store에 panelQueueMiddleware 등록 필요
|
||||
2. **리듀서 확장**: panelReducer에 큐 관련 상태 추가 필요
|
||||
3. **기존 코드**: 기존 pushPanel 등과 병행 사용 가능
|
||||
|
||||
---
|
||||
|
||||
**다음**: [사용 패턴 및 예제 →](./05-usage-patterns.md)
|
||||
Reference in New Issue
Block a user