Actions 기반 디스패칭
Actions 기반 디스패칭은 Context-Action에서 액션을 디스패치하는 더 직관적이고 함수형 접근 방식을 제공합니다. 기존의 registry.dispatch() 메서드 대신 registry.actions 속성을 통해 액션을 직접 함수로 호출할 수 있습니다.
개요
actions 속성은 각 등록된 액션이 호출 가능한 함수가 되는 함수형 인터페이스를 제공합니다. 이 접근 방식은 향상된 타입 안전성과 더 직관적인 구문으로 더 나은 개발자 경험을 제공합니다.
// 기존 방식
await registry.dispatch('userLogin', { userId: '123', email: 'user@example.com' });
// Actions 기반 방식
await registry.actions.userLogin({ userId: '123', email: 'user@example.com' });기본 사용법
1. 액션 타입 정의
먼저 ActionPayloadMap 인터페이스를 사용하여 액션 타입을 정의합니다:
interface AppActions extends ActionPayloadMap {
userLogin: { userId: string; email: string };
userLogout: void;
processData: { data: any; type: string };
sendNotification: { message: string; userId: string };
resetApp: void;
}2. ActionRegister 생성
액션 타입과 함께 ActionRegister 인스턴스를 생성합니다:
const registry = new ActionRegister<AppActions>({
name: 'MyApp',
registry: { debug: true }
});3. 핸들러 등록
액션에 대한 핸들러를 등록합니다:
registry.register('userLogin', (payload) => {
console.log('사용자 로그인:', payload.userId, payload.email);
return { success: true };
}, { id: 'login-handler', priority: 100 });
registry.register('userLogout', () => {
console.log('사용자 로그아웃');
return { success: true };
}, { id: 'logout-handler', priority: 100 });4. 액션 디스패치
Actions 기반 접근 방식을 사용하여 액션을 디스패치합니다:
// 페이로드가 있는 액션
await registry.actions.userLogin({ userId: '123', email: 'user@example.com' });
await registry.actions.processData({ data: { name: 'test' }, type: 'json' });
await registry.actions.sendNotification({ message: '안녕하세요!', userId: '123' });
// 페이로드가 없는 액션
await registry.actions.userLogout();
await registry.actions.resetApp();5. 결과 수집이 있는 액션
상세한 실행 결과가 필요한 경우 actionsWithResult를 사용하세요:
// 결과 수집이 있는 액션
const loginResult = await registry.actionsWithResult.userLogin({
userId: '123',
email: 'user@example.com'
});
console.log('로그인 결과:', {
success: loginResult.success,
duration: loginResult.execution.duration,
handlersExecuted: loginResult.execution.handlersExecuted,
results: loginResult.results
});
// 페이로드가 없는 액션의 결과 수집
const logoutResult = await registry.actionsWithResult.userLogout();
console.log('로그아웃 결과:', logoutResult);고급 기능
옵션 지원
Actions 기반 디스패칭은 기존 디스패칭과 동일한 모든 옵션을 지원합니다:
// 실행 옵션과 함께
await registry.actions.processData(
{ data: { name: 'test2' }, type: 'json' },
{ executionMode: 'parallel' }
);
// 디바운스와 함께
await registry.actions.sendNotification(
{ message: '디바운스된 메시지', userId: '456' },
{ debounce: 100 }
);
// 스로틀과 함께
await registry.actions.userLogin(
{ userId: '789', email: 'throttled@example.com' },
{ throttle: 1000 }
);타입 안전성
Actions 기반 접근 방식은 훌륭한 타입 안전성을 제공합니다:
// ✅ 올바른 사용법 - TypeScript가 자동완성을 제공합니다
await registry.actions.userLogin({ userId: '123', email: 'user@example.com' });
// ❌ TypeScript 오류 - 잘못된 페이로드 구조
// await registry.actions.userLogin({ wrongField: 'value' });
// ❌ TypeScript 오류 - void 액션에는 페이로드가 허용되지 않음
// await registry.actions.userLogout({ payload: 'should not exist' });필터링 및 고급 옵션
모든 고급 필터링 및 실행 옵션을 사용할 수 있습니다:
// 필터링과 함께
await registry.actions.processData(
{ data: { name: 'test' }, type: 'json' },
{
filter: {
handlerIds: ['data-processor'],
priority: { min: 80 }
}
}
);
// 결과 수집과 함께
const result = await registry.actions.processData(
{ data: { name: 'test' }, type: 'json' },
{
result: { collect: true, strategy: 'all' }
}
);장점
1. 직관적인 구문
Actions 기반 디스패칭은 더 자연스럽고 함수형으로 느껴집니다:
// 더 직관적
await registry.actions.userLogin(credentials);
// vs 기존 방식
await registry.dispatch('userLogin', credentials);2. 더 나은 타입 안전성
자동완성과 컴파일 타임 오류 검사가 포함된 완전한 TypeScript 지원:
// IDE가 액션 이름에 대한 자동완성을 표시합니다
registry.actions. // ← 표시: userLogin, userLogout, processData 등
// IDE가 페이로드 구조에 대한 자동완성을 표시합니다
registry.actions.userLogin({ // ← 표시: userId, email 속성3. 일관된 API
페이로드가 있는지 여부에 관계없이 모든 액션이 동일한 패턴을 따릅니다:
// 페이로드가 있는 액션
await registry.actions.userLogin(payload);
await registry.actions.processData(payload);
// 페이로드가 없는 액션
await registry.actions.userLogout();
await registry.actions.resetApp();4. 전체 기능 지원
모든 기존 디스패치 기능을 사용할 수 있습니다:
- 실행 모드 (순차, 병렬, 경쟁)
- 필터링 (핸들러 ID, 우선순위, 사용자 정의 필터별)
- 스로틀링 및 디바운싱
- 결과 수집
- 중단 신호
- 오류 처리
기존 디스패치에서 마이그레이션
현재 기존 디스패치를 사용하고 있다면, 마이그레이션은 간단합니다:
// 이전
await registry.dispatch('userLogin', { userId: '123', email: 'user@example.com' });
await registry.dispatch('userLogout', undefined);
await registry.dispatch('processData', { data: {} }, { executionMode: 'parallel' });
// 이후
await registry.actions.userLogin({ userId: '123', email: 'user@example.com' });
await registry.actions.userLogout();
await registry.actions.processData({ data: {} }, { executionMode: 'parallel' });모범 사례
1. 설명적인 액션 이름 사용
무엇을 하는지 명확하게 설명하는 액션 이름을 선택하세요:
// 좋음
userLogin, processPayment, sendNotification
// 피하기
action1, doSomething, handle2. 명확한 페이로드 타입 정의
페이로드에 대해 구체적인 타입을 사용하세요:
interface AppActions extends ActionPayloadMap {
userLogin: {
userId: string;
email: string;
rememberMe?: boolean;
};
processPayment: {
amount: number;
currency: string;
paymentMethod: 'card' | 'bank' | 'paypal';
};
}3. 적절한 오류 처리
핸들러에서 적절한 오류 처리를 사용하세요:
registry.register('processPayment', async (payload) => {
try {
const result = await paymentService.process(payload);
return { success: true, transactionId: result.id };
} catch (error) {
console.error('결제 실패:', error);
throw error; // 프레임워크가 처리하도록 다시 던지기
}
});4. 옵션을 현명하게 사용
사용 사례에 따라 실행 옵션을 적용하세요:
// 반복될 수 있는 사용자 상호작용의 경우
await registry.actions.saveDraft(
{ content: draftContent },
{ debounce: 500 }
);
// 비용이 많이 드는 작업의 경우
await registry.actions.generateReport(
{ reportType: 'monthly' },
{ executionMode: 'parallel' }
);예제
완전한 예제
import { ActionRegister, ActionPayloadMap } from '@context-action/core';
interface ECommerceActions extends ActionPayloadMap {
addToCart: { productId: string; quantity: number };
removeFromCart: { productId: string };
checkout: { paymentMethod: string };
clearCart: void;
}
async function ecommerceExample() {
const registry = new ActionRegister<ECommerceActions>({
name: 'ECommerce',
registry: { debug: true }
});
// 핸들러 등록
registry.register('addToCart', (payload) => {
console.log(`장바구니에 상품 ${payload.productId} ${payload.quantity}개 추가`);
return { success: true };
});
registry.register('removeFromCart', (payload) => {
console.log(`장바구니에서 상품 ${payload.productId} 제거`);
return { success: true };
});
registry.register('checkout', async (payload) => {
console.log(`${payload.paymentMethod}로 결제 처리 중`);
// 결제 처리 시뮬레이션
await new Promise(resolve => setTimeout(resolve, 1000));
return { success: true, orderId: 'order-123' };
});
registry.register('clearCart', () => {
console.log('장바구니 비움');
return { success: true };
});
// Actions 기반 디스패칭 사용
await registry.actions.addToCart({ productId: 'prod-1', quantity: 2 });
await registry.actions.addToCart({ productId: 'prod-2', quantity: 1 });
// 옵션과 함께 체크아웃
const result = await registry.actions.checkout(
{ paymentMethod: 'card' },
{ executionMode: 'parallel' }
);
console.log('체크아웃 결과:', result);
// 장바구니 비우기
await registry.actions.clearCart();
}
ecommerceExample().catch(console.error);Actions 기반 디스패칭은 기존 접근 방식의 모든 힘과 유연성을 유지하면서 Context-Action으로 작업하는 더 직관적이고 개발자 친화적인 방법을 제공합니다.