[251105] feat: dispatchHelper.js
🕐 커밋 시간: 2025. 11. 05. 10:26:56 📊 변경 통계: • 총 파일: 1개 📁 추가된 파일: + com.twin.app.shoptime/src/utils/dispatchHelper.js 🔧 주요 변경 내용: • 공통 유틸리티 함수 최적화
This commit is contained in:
444
com.twin.app.shoptime/src/utils/dispatchHelper.js
Normal file
444
com.twin.app.shoptime/src/utils/dispatchHelper.js
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
// src/utils/dispatchHelper.js
|
||||||
|
// Redux Thunk Sequential Dispatch Helper Functions
|
||||||
|
//
|
||||||
|
// ==================== 실제 사용 예시 ====================
|
||||||
|
//
|
||||||
|
// 이 Helper 함수들은 프로젝트의 실제 dispatch 패턴을 분석하여 설계되었습니다.
|
||||||
|
// 다음과 같은 기존 패턴들을 간소화할 수 있습니다:
|
||||||
|
//
|
||||||
|
// 1. setTimeout을 이용한 순차 dispatch (homeActions.js)
|
||||||
|
// Before:
|
||||||
|
// const onSuccess = (response) => {
|
||||||
|
// dispatch({ type: types.GET_HOME_TERMS, payload: response.data });
|
||||||
|
// dispatch({ type: types.SET_TERMS_ID_MAP, payload: termsIdMap });
|
||||||
|
// setTimeout(() => { dispatch(getTermsAgreeYn()); }, 0);
|
||||||
|
// };
|
||||||
|
// After:
|
||||||
|
// dispatch(createSequentialDispatch([
|
||||||
|
// { type: types.GET_HOME_TERMS, payload: response.data },
|
||||||
|
// { type: types.SET_TERMS_ID_MAP, payload: termsIdMap },
|
||||||
|
// getTermsAgreeYn()
|
||||||
|
// ]));
|
||||||
|
//
|
||||||
|
// 2. API 성공 후 재조회 (cartActions.js)
|
||||||
|
// Before:
|
||||||
|
// const onSuccess = (response) => {
|
||||||
|
// dispatch({ type: types.ADD_TO_CART, payload: response.data.data });
|
||||||
|
// dispatch(getMyInfoCartSearch({ mbrNo }));
|
||||||
|
// };
|
||||||
|
// After:
|
||||||
|
// createApiThunkWithChain(
|
||||||
|
// (d, gs, onS, onF) => TAxios(d, gs, 'post', URLS.ADD_TO_CART, {}, props, onS, onF),
|
||||||
|
// [
|
||||||
|
// (response) => ({ type: types.ADD_TO_CART, payload: response.data.data }),
|
||||||
|
// (response) => getMyInfoCartSearch({ mbrNo: props.mbrNo })
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// 3. Loading 상태 관리 (mainActions.js)
|
||||||
|
// Before:
|
||||||
|
// dispatch(changeAppStatus({ showLoadingPanel: { show: true } }));
|
||||||
|
// TAxios(..., onSuccess, onFail);
|
||||||
|
// const onSuccess = () => { ...; dispatch(changeAppStatus({ show: false })); };
|
||||||
|
// const onFail = () => { dispatch(changeAppStatus({ show: false })); };
|
||||||
|
// After:
|
||||||
|
// withLoadingState(
|
||||||
|
// (dispatch, getState) => TAxiosPromise(dispatch, getState, 'get', URLS.API, props, {})
|
||||||
|
// .then((response) => dispatch({ type: types.ACTION, payload: response.data.data }))
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// 상세한 문서: .docs/plan/Promise/Redux-Sequential.md
|
||||||
|
// ========================================================
|
||||||
|
|
||||||
|
import { changeAppStatus } from '../actions/commonActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 여러 dispatch를 순차적으로 실행하는 Helper 함수
|
||||||
|
*
|
||||||
|
* @param {Array<Function|Object>} dispatchActions - 실행할 dispatch들의 배열
|
||||||
|
* - Function: thunk action creator (props) => (dispatch, getState) => {}
|
||||||
|
* - Object: plain action { type: 'ACTION_TYPE', payload: data }
|
||||||
|
* @param {Object} options - 옵션 객체
|
||||||
|
* @param {number} options.delay - 각 dispatch 간 지연 시간 (ms, 기본값: 0)
|
||||||
|
* @param {boolean} options.stopOnError - 에러 발생 시 중단 여부 (기본값: false)
|
||||||
|
* @returns {Function} Redux thunk 함수 (dispatch, getState) => Promise
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 1: 여러 plain action 순차 실행
|
||||||
|
* dispatch(createSequentialDispatch([
|
||||||
|
* { type: types.SET_LOADING, payload: true },
|
||||||
|
* { type: types.UPDATE_DATA, payload: data },
|
||||||
|
* { type: types.SET_LOADING, payload: false }
|
||||||
|
* ]));
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 2: thunk와 plain action 혼합
|
||||||
|
* dispatch(createSequentialDispatch([
|
||||||
|
* { type: types.GET_HOME_TERMS, payload: response.data },
|
||||||
|
* { type: types.SET_TERMS_ID_MAP, payload: termsIdMap },
|
||||||
|
* getTermsAgreeYn() // thunk action
|
||||||
|
* ]));
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 3: delay 옵션 사용
|
||||||
|
* dispatch(createSequentialDispatch([
|
||||||
|
* fetchUserData(),
|
||||||
|
* fetchCartData(),
|
||||||
|
* fetchOrderData()
|
||||||
|
* ], { delay: 100, stopOnError: true }));
|
||||||
|
*/
|
||||||
|
export const createSequentialDispatch = (dispatchActions, options) =>
|
||||||
|
(dispatch, getState) => {
|
||||||
|
const config = options || {};
|
||||||
|
const delay = config.delay || 0;
|
||||||
|
const stopOnError = config.stopOnError !== undefined ? config.stopOnError : false;
|
||||||
|
|
||||||
|
return dispatchActions.reduce(
|
||||||
|
(promise, action, index) => {
|
||||||
|
return promise
|
||||||
|
.then(() => {
|
||||||
|
// delay가 설정되어 있고 첫 번째가 아닌 경우 지연
|
||||||
|
if (delay > 0 && index > 0) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, delay));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
// action 실행
|
||||||
|
const result = dispatch(action);
|
||||||
|
|
||||||
|
// Promise인 경우 대기
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return Promise.resolve(result);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('createSequentialDispatch error at index', index, error);
|
||||||
|
|
||||||
|
// stopOnError가 true면 에러를 다시 throw
|
||||||
|
if (stopOnError) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopOnError가 false면 계속 진행
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Promise.resolve()
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 호출 후 콜백에서 여러 dispatch를 자동으로 체이닝하는 Helper
|
||||||
|
* TAxios의 onSuccess/onFail 콜백 패턴과 호환
|
||||||
|
*
|
||||||
|
* @param {Function} apiCallFactory - API 호출을 생성하는 함수
|
||||||
|
* (dispatch, getState, onSuccess, onFail) => void
|
||||||
|
* @param {Array<Function|Object>} successDispatchActions - 성공 시 실행할 dispatch들
|
||||||
|
* @param {Function|Object|null} errorDispatch - 에러 시 실행할 dispatch (선택)
|
||||||
|
* @returns {Function} Redux thunk 함수
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 1: API 호출 후 여러 dispatch 자동 실행
|
||||||
|
* export const addToCart = (props) =>
|
||||||
|
* createApiThunkWithChain(
|
||||||
|
* (dispatch, getState, onSuccess, onFail) => {
|
||||||
|
* TAxios(dispatch, getState, 'post', URLS.ADD_TO_CART, {}, props, onSuccess, onFail);
|
||||||
|
* },
|
||||||
|
* [
|
||||||
|
* (response) => ({ type: types.ADD_TO_CART, payload: response.data.data }),
|
||||||
|
* (response) => getMyInfoCartSearch({ mbrNo: response.data.data.mbrNo })
|
||||||
|
* ]
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 2: 에러 처리 포함
|
||||||
|
* export const registerDevice = (params) =>
|
||||||
|
* createApiThunkWithChain(
|
||||||
|
* (dispatch, getState, onSuccess, onFail) => {
|
||||||
|
* TAxios(dispatch, getState, 'post', URLS.REGISTER_DEVICE, {}, params, onSuccess, onFail);
|
||||||
|
* },
|
||||||
|
* [
|
||||||
|
* (response) => ({ type: types.REGISTER_DEVICE, payload: response.data.data }),
|
||||||
|
* getAuthenticationCode(),
|
||||||
|
* fetchCurrentUserHomeTerms()
|
||||||
|
* ],
|
||||||
|
* (error) => ({ type: types.API_ERROR, payload: error })
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
export const createApiThunkWithChain = (
|
||||||
|
apiCallFactory,
|
||||||
|
successDispatchActions,
|
||||||
|
errorDispatch
|
||||||
|
) => (dispatch, getState) => {
|
||||||
|
const actions = successDispatchActions || [];
|
||||||
|
|
||||||
|
const enhancedOnSuccess = (response) => {
|
||||||
|
// 성공 시 순차적으로 dispatch 실행
|
||||||
|
actions.forEach((action, index) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof action === 'function') {
|
||||||
|
// action이 함수인 경우 (동적 action creator)
|
||||||
|
// response를 인자로 전달하여 실행
|
||||||
|
const dispatchAction = action(response);
|
||||||
|
dispatch(dispatchAction);
|
||||||
|
} else {
|
||||||
|
// action이 객체인 경우 (plain action)
|
||||||
|
dispatch(action);
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const enhancedOnFail = (error) => {
|
||||||
|
console.error('createApiThunkWithChain error:', error);
|
||||||
|
|
||||||
|
if (errorDispatch) {
|
||||||
|
if (typeof errorDispatch === 'function') {
|
||||||
|
// errorDispatch가 함수인 경우
|
||||||
|
const dispatchAction = errorDispatch(error);
|
||||||
|
dispatch(dispatchAction);
|
||||||
|
} else {
|
||||||
|
// errorDispatch가 객체인 경우
|
||||||
|
dispatch(errorDispatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// API 호출 실행
|
||||||
|
return apiCallFactory(dispatch, getState, enhancedOnSuccess, enhancedOnFail);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 호출 thunk의 Loading 상태를 자동으로 관리하는 Helper
|
||||||
|
* changeAppStatus로 showLoadingPanel을 자동 on/off
|
||||||
|
*
|
||||||
|
* @param {Function} thunk - API 호출 thunk 함수
|
||||||
|
* @param {Object} options - 옵션 객체
|
||||||
|
* @param {string} options.loadingType - 로딩 타입 (기본값: 'wait')
|
||||||
|
* @param {Array} options.successDispatch - 성공 시 추가 dispatch (선택)
|
||||||
|
* @param {Array} options.errorDispatch - 에러 시 추가 dispatch (선택)
|
||||||
|
* @returns {Function} Redux thunk 함수
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 1: 기본 로딩 관리
|
||||||
|
* export const getProductDetail = (props) =>
|
||||||
|
* withLoadingState(
|
||||||
|
* (dispatch, getState) => {
|
||||||
|
* return TAxiosPromise(dispatch, getState, 'get', URLS.GET_PRODUCT_DETAIL, props, {})
|
||||||
|
* .then((response) => {
|
||||||
|
* dispatch({ type: types.GET_PRODUCT_DETAIL, payload: response.data.data });
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 2: 성공/에러 시 추가 dispatch
|
||||||
|
* export const fetchUserData = (userId) =>
|
||||||
|
* withLoadingState(
|
||||||
|
* fetchUser(userId),
|
||||||
|
* {
|
||||||
|
* successDispatch: [
|
||||||
|
* fetchCart(userId),
|
||||||
|
* fetchOrders(userId)
|
||||||
|
* ],
|
||||||
|
* errorDispatch: [
|
||||||
|
* (error) => ({ type: types.SHOW_ERROR_MESSAGE, payload: error.message })
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
*/
|
||||||
|
export const withLoadingState = (thunk, options) => (dispatch, getState) => {
|
||||||
|
const config = options || {};
|
||||||
|
const loadingType = config.loadingType || 'wait';
|
||||||
|
const successDispatch = config.successDispatch || [];
|
||||||
|
const errorDispatch = config.errorDispatch || [];
|
||||||
|
|
||||||
|
// 로딩 시작
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: loadingType } }));
|
||||||
|
|
||||||
|
// thunk 실행
|
||||||
|
const result = dispatch(thunk);
|
||||||
|
|
||||||
|
// Promise인 경우 처리
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
return result
|
||||||
|
.then((res) => {
|
||||||
|
// 로딩 종료
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
|
||||||
|
// 성공 시 추가 dispatch 실행
|
||||||
|
successDispatch.forEach((action) => {
|
||||||
|
if (typeof action === 'function') {
|
||||||
|
dispatch(action(res));
|
||||||
|
} else {
|
||||||
|
dispatch(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
// 로딩 종료
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
|
||||||
|
// 에러 시 추가 dispatch 실행
|
||||||
|
errorDispatch.forEach((action) => {
|
||||||
|
if (typeof action === 'function') {
|
||||||
|
dispatch(action(error));
|
||||||
|
} else {
|
||||||
|
dispatch(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 동기 실행인 경우 (Promise가 아님)
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 조건에 따라 다른 dispatch를 실행하는 Helper
|
||||||
|
* getState() 결과를 기반으로 분기 처리
|
||||||
|
*
|
||||||
|
* @param {Function} condition - 조건을 반환하는 함수 (state) => boolean
|
||||||
|
* @param {Function|Array|Object} trueDispatch - 조건 true 시 실행할 dispatch
|
||||||
|
* @param {Function|Array|Object|null} falseDispatch - 조건 false 시 실행할 dispatch (선택)
|
||||||
|
* @returns {Function} Redux thunk 함수
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 1: 단일 action 조건부 실행
|
||||||
|
* dispatch(createConditionalDispatch(
|
||||||
|
* (state) => state.common.appStatus.isAlarmEnabled === 'Y',
|
||||||
|
* addReservation(reservationData),
|
||||||
|
* deleteReservation(showId)
|
||||||
|
* ));
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 2: 여러 action 배열로 실행
|
||||||
|
* dispatch(createConditionalDispatch(
|
||||||
|
* (state) => state.common.appStatus.loginUserData.userNumber,
|
||||||
|
* [
|
||||||
|
* fetchUserProfile(),
|
||||||
|
* fetchUserCart(),
|
||||||
|
* fetchUserOrders()
|
||||||
|
* ],
|
||||||
|
* [
|
||||||
|
* { type: types.SHOW_LOGIN_REQUIRED_POPUP }
|
||||||
|
* ]
|
||||||
|
* ));
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시 3: false 조건 없이
|
||||||
|
* dispatch(createConditionalDispatch(
|
||||||
|
* (state) => state.cart.items.length > 0,
|
||||||
|
* proceedToCheckout()
|
||||||
|
* ));
|
||||||
|
*/
|
||||||
|
export const createConditionalDispatch = (
|
||||||
|
condition,
|
||||||
|
trueDispatch,
|
||||||
|
falseDispatch
|
||||||
|
) => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const shouldExecuteTrue = condition(state);
|
||||||
|
|
||||||
|
const actionsToDispatch = shouldExecuteTrue ? trueDispatch : falseDispatch;
|
||||||
|
|
||||||
|
// dispatch할 것이 없으면 종료
|
||||||
|
if (!actionsToDispatch) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 배열이 아니면 배열로 변환
|
||||||
|
const actions = Array.isArray(actionsToDispatch)
|
||||||
|
? actionsToDispatch
|
||||||
|
: [actionsToDispatch];
|
||||||
|
|
||||||
|
// 각 action 실행
|
||||||
|
actions.forEach((action) => {
|
||||||
|
if (typeof action === 'function') {
|
||||||
|
// thunk action
|
||||||
|
dispatch(action);
|
||||||
|
} else {
|
||||||
|
// plain action
|
||||||
|
dispatch(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 여러 API 호출을 병렬로 실행하고 모든 결과를 기다리는 Helper
|
||||||
|
* Promise.all을 사용하여 동시 실행
|
||||||
|
*
|
||||||
|
* @param {Array<Function>} thunks - 실행할 thunk 함수들의 배열
|
||||||
|
* @param {Object} options - 옵션
|
||||||
|
* @param {boolean} options.withLoading - 로딩 상태 관리 여부 (기본값: false)
|
||||||
|
* @returns {Function} Redux thunk 함수
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // 사용 예시: 여러 API를 동시에 호출
|
||||||
|
* dispatch(createParallelDispatch([
|
||||||
|
* fetchUserProfile(),
|
||||||
|
* fetchUserCart(),
|
||||||
|
* fetchUserOrders()
|
||||||
|
* ], { withLoading: true }));
|
||||||
|
*/
|
||||||
|
export const createParallelDispatch = (thunks, options) => (dispatch, getState) => {
|
||||||
|
const config = options || {};
|
||||||
|
const withLoading = config.withLoading || false;
|
||||||
|
|
||||||
|
if (withLoading) {
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: true, type: 'wait' } }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = thunks.map((thunk) => {
|
||||||
|
const result = dispatch(thunk);
|
||||||
|
// Promise가 아닌 경우 Promise로 변환
|
||||||
|
return Promise.resolve(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then((results) => {
|
||||||
|
if (withLoading) {
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (withLoading) {
|
||||||
|
dispatch(changeAppStatus({ showLoadingPanel: { show: false } }));
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// ==================== 편의 함수들 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* setTimeout을 Promise로 래핑한 delay 함수
|
||||||
|
* @param {number} ms - 지연 시간 (밀리초)
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dispatch를 Promise로 변환하는 Helper
|
||||||
|
* @param {Function} thunk - thunk 함수
|
||||||
|
* @returns {Function} Promise를 반환하는 thunk
|
||||||
|
*/
|
||||||
|
export const promisifyDispatch = (thunk) => (dispatch, getState) => {
|
||||||
|
const result = dispatch(thunk);
|
||||||
|
|
||||||
|
if (result && typeof result.then === 'function') {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(result);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user