Action Only 메서드
createActionContext
에서 제공하는 Action Only 패턴 메서드의 완전한 API 레퍼런스입니다.
개요
Action Only 패턴은 상태 관리 없이 타입 안전한 액션 디스패치를 제공합니다. 이 패턴은 이벤트 시스템, 커맨드 패턴, 비즈니스 로직 오케스트레이션, 그리고 로컬 상태 없이 액션 처리가 필요한 시나리오에 이상적입니다.
업데이트 정보: 2025-08-17 - 더 나은 타입 안전성과 성능 개선이 포함된 새로운 API 메서드가 추가되었습니다.
최신 변경사항: Priority JSON 기반 작업 상태 관리 시스템이 추가되어 문서 간 동기화가 자동화되었습니다. YAML 프론트메터 표준 형식을 도입하여 문서 병합 및 메타데이터 관리가 개선되었습니다.
핵심 메서드
createActionContext<T>(contextName)
타입 안전한 액션 디스패치와 핸들러 등록을 위한 액션 컨텍스트를 생성합니다.
매개변수:
contextName
: 액션 컨텍스트의 고유 식별자
반환값:
{
Provider: React.ComponentType,
useActionDispatch: () => ActionDispatcher<T>,
useActionHandler: (actionName, handler, options?) => void
}
예제:
interface AppActions extends ActionPayloadMap {
updateUser: { id: string; name: string };
deleteUser: { id: string };
}
const { Provider, useActionDispatch, useActionHandler } =
createActionContext<AppActions>('App');
액션 디스패처 메서드
dispatch(actionName, payload)
지정된 페이로드로 등록된 모든 핸들러에 액션을 디스패치합니다.
매개변수:
actionName
: 디스패치할 액션의 이름payload
: 액션 페이로드 데이터
반환값: Promise<ActionResult[]>
- 모든 핸들러의 결과
function UserComponent() {
const dispatch = useActionDispatch();
const handleUpdate = async () => {
try {
const results = await dispatch('updateUser', {
id: '123',
name: 'John Doe'
});
console.log('액션 결과:', results);
} catch (error) {
console.error('액션 실패:', error);
}
};
return <button onClick={handleUpdate}>사용자 업데이트</button>;
}
dispatch.async(actionName, payload)
표준 dispatch 메서드의 별칭으로, 비동기 동작을 명시적으로 나타냅니다.
매개변수:
actionName
: 디스패치할 액션의 이름payload
: 액션 페이로드 데이터
반환값: Promise<ActionResult[]>
const results = await dispatch.async('processData', { data: 'example' });
핸들러 등록
useActionHandler(actionName, handler, options?)
지정된 액션에 대한 액션 핸들러를 등록합니다.
매개변수:
actionName
: 처리할 액션의 이름handler
: 핸들러 함수(payload, controller) => Promise<any> | any
options
: 선택적 설정 객체
핸들러 함수 시그니처:
type ActionHandler<TPayload> = (
payload: TPayload,
controller: PipelineController
) => Promise<any> | any;
옵션:
interface HandlerOptions {
priority?: number; // 실행 우선순위 (높을수록 먼저, 기본값: 0)
id?: string; // 고유 핸들러 식별자
once?: boolean; // 한 번만 실행 후 등록 해제
}
예제:
function UserHandler() {
const dispatch = useActionDispatch();
useActionHandler('updateUser', useCallback(async (payload, controller) => {
try {
// 페이로드 검증
if (!payload.id) {
controller.abort('사용자 ID가 필요합니다');
return;
}
// 비즈니스 로직
const result = await userService.updateUser(payload.id, {
name: payload.name
});
// 다른 핸들러를 위한 결과 설정
controller.setResult({
step: 'user-update',
success: true,
userId: result.id
});
return { success: true, user: result };
} catch (error) {
controller.abort(`사용자 업데이트 실패: ${error.message}`);
}
}, []), { priority: 100, id: 'user-updater' });
return null;
}
파이프라인 컨트롤러 메서드
액션 핸들러의 controller
매개변수는 고급 파이프라인 제어를 제공합니다.
controller.abort(reason, error?)
액션 파이프라인 실행을 중단합니다.
매개변수:
reason
: 중단 이유error?
: 선택적 에러 객체
useActionHandler('validateData', (payload, controller) => {
if (!payload.data) {
controller.abort('데이터가 필요합니다');
return;
}
if (!isValid(payload.data)) {
controller.abort('잘못된 데이터 형식', new ValidationError());
return;
}
});
controller.modifyPayload(modifier)
파이프라인의 후속 핸들러를 위해 페이로드를 수정합니다.
매개변수:
modifier
: 현재 페이로드를 받아 수정된 페이로드를 반환하는 함수
useActionHandler('enrichData', (payload, controller) => {
controller.modifyPayload(current => ({
...current,
timestamp: Date.now(),
userId: getCurrentUserId(),
sessionId: getSessionId()
}));
return { enriched: true };
}, { priority: 100 }); // 먼저 실행되도록 높은 우선순위
controller.setResult(result)
나중 핸들러가 액세스할 수 있는 결과를 설정합니다.
매개변수:
result
: 저장할 결과 객체
useActionHandler('processPayment', async (payload, controller) => {
const transaction = await paymentService.process(payload);
controller.setResult({
step: 'payment',
transactionId: transaction.id,
amount: transaction.amount,
success: true
});
return transaction;
}, { priority: 90 });
controller.getResults()
이전 핸들러들이 설정한 모든 결과를 가져옵니다.
반환값: 결과 객체 배열
useActionHandler('sendReceipt', async (payload, controller) => {
const results = controller.getResults();
const paymentResult = results.find(r => r.step === 'payment');
if (paymentResult?.success) {
await emailService.sendReceipt({
transactionId: paymentResult.transactionId,
amount: paymentResult.amount,
email: payload.email
});
}
}, { priority: 80 }); // 결제 후 실행되도록 낮은 우선순위
controller.getPayload()
현재 (수정될 수 있는) 페이로드를 가져옵니다.
반환값: 현재 페이로드 객체
useActionHandler('logAction', (_, controller) => {
const currentPayload = controller.getPayload();
console.log('최종 페이로드:', currentPayload);
return { logged: true };
}, { priority: 10 }); // 마지막에 실행되도록 낮은 우선순위
핸들러 패턴
순차 처리
우선순위 순서로 핸들러가 실행되어 순차 처리됩니다:
function SequentialHandlers() {
// 1단계: 검증 (우선순위 100)
useActionHandler('processOrder', (payload, controller) => {
if (!validateOrder(payload)) {
controller.abort('잘못된 주문');
}
return { step: 'validation', valid: true };
}, { priority: 100 });
// 2단계: 결제 (우선순위 90)
useActionHandler('processOrder', async (payload, controller) => {
const payment = await processPayment(payload.paymentInfo);
controller.setResult({ step: 'payment', transactionId: payment.id });
return payment;
}, { priority: 90 });
// 3단계: 주문 처리 (우선순위 80)
useActionHandler('processOrder', async (payload, controller) => {
const results = controller.getResults();
const paymentResult = results.find(r => r.step === 'payment');
if (paymentResult) {
const order = await fulfillOrder(payload, paymentResult.transactionId);
return { step: 'fulfillment', orderId: order.id };
}
}, { priority: 80 });
return null;
}
병렬 처리
같은 액션을 독립적으로 처리하는 여러 핸들러:
function ParallelHandlers() {
// 애널리틱스 추적 (독립적)
useActionHandler('userAction', async (payload) => {
await analytics.track(payload.action, payload.data);
return { provider: 'analytics', tracked: true };
}, { id: 'analytics' });
// 에러 모니터링 (독립적)
useActionHandler('userAction', async (payload) => {
await errorMonitor.log(payload.action, payload.context);
return { provider: 'monitor', logged: true };
}, { id: 'monitor' });
// 사용자 피드백 (독립적)
useActionHandler('userAction', (payload) => {
showToast(`액션 ${payload.action}이 완료되었습니다`);
return { provider: 'ui', notified: true };
}, { id: 'ui-feedback' });
return null;
}
에러 복구
우아한 에러 처리 및 복구:
function ErrorRecoveryHandlers() {
// 주 핸들러
useActionHandler('apiCall', async (payload, controller) => {
try {
const result = await primaryApi.call(payload.endpoint, payload.data);
controller.setResult({ provider: 'primary', success: true, data: result });
return result;
} catch (error) {
controller.setResult({ provider: 'primary', success: false, error });
// 중단하지 않고 폴백 핸들러가 시도하도록 함
return { error: error.message };
}
}, { priority: 100, id: 'primary-api' });
// 폴백 핸들러
useActionHandler('apiCall', async (payload, controller) => {
const results = controller.getResults();
const primaryFailed = results.some(r => r.provider === 'primary' && !r.success);
if (primaryFailed) {
try {
const result = await fallbackApi.call(payload.endpoint, payload.data);
return { success: true, data: result, fallback: true };
} catch (error) {
controller.abort(`모든 API 제공자 실패: ${error.message}`);
}
}
return { skipped: true, reason: 'primary-succeeded' };
}, { priority: 90, id: 'fallback-api' });
return null;
}
고급 사용 사례
커맨드 패턴 구현
interface CommandActions extends ActionPayloadMap {
executeCommand: {
type: 'create' | 'update' | 'delete';
entity: string;
data: any;
};
}
function CommandProcessor() {
const dispatch = useActionDispatch();
useActionHandler('executeCommand', async (payload, controller) => {
const command = createCommand(payload.type, payload.entity, payload.data);
try {
const result = await command.execute();
// 커맨드 실행 로그
dispatch('trackEvent', {
event: 'command_executed',
data: {
commandType: payload.type,
entity: payload.entity,
success: true
}
});
return result;
} catch (error) {
// 커맨드 실패 로그
dispatch('logError', {
error: error.message,
context: { command: payload },
severity: 'high'
});
controller.abort(`커맨드 실행 실패: ${error.message}`);
}
}, [dispatch]);
return null;
}
이벤트 집계
function EventAggregator() {
const dispatch = useActionDispatch();
useActionHandler('userInteraction', useCallback((payload, controller) => {
// 상호작용 데이터 수집
const interactionData = {
type: payload.type,
element: payload.element,
timestamp: Date.now(),
sessionId: getSessionId()
};
// 배치를 위해 저장
addToInteractionBuffer(interactionData);
// 버퍼가 가득 찬 경우 배치 처리 트리거
if (isBufferFull()) {
dispatch('flushInteractions', { interactions: getBufferContents() });
clearBuffer();
}
return { buffered: true };
}, [dispatch]), { id: 'interaction-aggregator' });
useActionHandler('flushInteractions', async (payload) => {
await analytics.batchTrack(payload.interactions);
return { flushed: payload.interactions.length };
}, { id: 'interaction-flusher' });
return null;
}
베스트 프랙티스
1. 핸들러 조직화
- 관련 기능을 위한 전용 핸들러 컴포넌트 생성
- 디버깅을 위해 의미 있는 핸들러 ID 사용
- 도메인이나 책임별로 핸들러 그룹화
2. 우선순위 관리
- 중요한 설정/검증에는 높은 우선순위 (90-100) 사용
- 비즈니스 로직에는 중간 우선순위 (50-80) 사용
- 정리/로깅에는 낮은 우선순위 (10-40) 사용
3. 에러 처리
- 실행을 중지해야 하는 중요한 실패에는
controller.abort()
사용 - 중요하지 않은 실패에는 에러 객체 반환
- 탄력성을 위한 폴백 핸들러 구현
4. 성능
- 재등록을 방지하기 위해 항상 핸들러를
useCallback
으로 감싸기 - 디버깅과 프로파일링을 위해 핸들러 ID 사용
- 핸들러에서 복잡한 계산 피하기 - 서비스에 위임
5. 테스트
- 직접 액션 디스패치를 사용하여 핸들러를 독립적으로 테스트
- 의존성과 서비스를 모킹
- 에러 시나리오와 엣지 케이스 테스트
관련 자료
- 액션 레지스트리 API - 액션 등록 및 관리
- 파이프라인 컨트롤러 API - 파이프라인 제어 메서드
- Action Only 예제 - 완전한 사용 예제