Skip to content

디스패치 메서드

다양한 수준의 제어와 결과 수집으로 액션 파이프라인을 트리거하는 다양한 방법.

핵심 디스패치 메서드

기본 디스패치

결과 수집 없이 간단한 액션 실행:

typescript
interface UserActions extends ActionPayloadMap {
  updateProfile: { userId: string; data: UserProfile };
  sendNotification: { message: string; type: 'info' | 'success' | 'error' };
}

const userRegister = new ActionRegister<UserActions>();

// 기본 디스패치 - 발사 후 계속
await userRegister.dispatch('updateProfile', {
  userId: '123',
  data: { name: 'John Doe', email: 'john@example.com' }
});

// void 반환 (결과 수집 없음)

결과 수집과 함께 디스패치

상세한 결과와 함께 포괄적인 실행:

typescript
const result = await userRegister.dispatchWithResult('updateProfile', 
  {
    userId: '123',
    data: { name: 'John Doe', email: 'john@example.com' }
  },
  { 
    result: { collect: true },  // 결과 수집 활성화
    timeout: 5000              // 선택적 타임아웃
  }
);

console.log(result);
// {
//   success: true,
//   aborted: false,
//   terminated: false,
//   results: [
//     { step: 'validation', success: true },
//     { step: 'update', userId: '123', updated: true },
//     { step: 'notification', sent: true }
//   ],
//   execution: {
//     handlersExecuted: 3,
//     startTime: 1640995200000,
//     endTime: 1640995200500,
//     duration: 500
//   }
// }

React 통합 디스패치

useActionDispatch 훅

React 컴포넌트에서의 기본 디스패치:

typescript
import { useActionDispatch } from './UserActionContext';

function UserComponent() {
  const dispatch = useActionDispatch();
  
  const handleUpdate = async () => {
    // 간단한 디스패치
    await dispatch('updateProfile', {
      userId: '123',
      data: { name: 'Updated Name' }
    });
  };
  
  return <button onClick={handleUpdate}>프로필 업데이트</button>;
}

useActionDispatchWithResult 훅

React 컴포넌트에서의 결과 수집:

typescript
import { useActionDispatchWithResult } from './UserActionContext';

function AdvancedUserComponent() {
  const { dispatchWithResult } = useActionDispatchWithResult();
  
  const handleComplexUpdate = async () => {
    try {
      const result = await dispatchWithResult('updateProfile', 
        {
          userId: '123',
          data: { name: 'Complex Update' }
        },
        { result: { collect: true } }
      );
      
      if (result.success) {
        console.log('업데이트 성공:', result.results);
      } else {
        console.error('업데이트 실패:', result.errors);
      }
      
    } catch (error) {
      console.error('작업 실패:', error);
    }
  };
  
  return <button onClick={handleComplexUpdate}>복합 업데이트</button>;
}

디스패치 옵션

타임아웃 구성

타임아웃으로 무한 대기를 방지합니다:

typescript
// 타임아웃과 함께 디스패치
const result = await actionRegister.dispatchWithResult('longOperation', 
  { data: 'large-dataset' },
  { 
    result: { collect: true },
    timeout: 10000  // 10초 타임아웃
  }
);

if (result.terminated) {
  console.log('작업 시간 초과');
}

결과 수집 옵션

어떤 결과를 수집할지 제어합니다:

typescript
// 모든 결과 수집
const fullResult = await actionRegister.dispatchWithResult('operation', payload, {
  result: { collect: true }
});

// 성공한 결과만 수집
const successResult = await actionRegister.dispatchWithResult('operation', payload, {
  result: { 
    collect: true,
    includeErrors: false  // 실패한 핸들러 제외
  }
});

// 메타데이터와 함께 결과 수집
const detailedResult = await actionRegister.dispatchWithResult('operation', payload, {
  result: { 
    collect: true,
    includeMetadata: true,  // 핸들러 ID, 우선순위, 타이밍 포함
    includePayload: true    // 최종 페이로드 상태 포함
  }
});

고급 디스패치 패턴

조건부 디스패치

typescript
function ConditionalDispatcher() {
  const dispatch = useActionDispatch();
  
  const handleUserAction = async (action: string, data: any) => {
    // 사용자 권한에 따른 조건부 디스패치
    const user = getCurrentUser();
    
    if (user.role === 'admin') {
      await dispatch('adminAction', { action, data, adminId: user.id });
    } else if (user.role === 'user') {
      await dispatch('userAction', { action, data, userId: user.id });
    } else {
      await dispatch('guestAction', { action, data });
    }
  };
  
  return <button onClick={() => handleUserAction('update', {})}>액션</button>;
}

배치 디스패치

typescript
function BatchDispatcher() {
  const dispatch = useActionDispatch();
  
  const handleBatchOperation = async (items: any[]) => {
    // 순차 배치 처리
    for (const item of items) {
      await dispatch('processItem', { item, batchId: generateId() });
    }
    
    // 최종 배치 완료
    await dispatch('completeBatch', { 
      itemCount: items.length,
      completedAt: Date.now()
    });
  };
  
  // 병렬 배치 처리
  const handleParallelBatch = async (items: any[]) => {
    const batchId = generateId();
    
    // 모든 항목을 병렬로 디스패치
    const promises = items.map(item => 
      dispatch('processItem', { item, batchId })
    );
    
    await Promise.all(promises);
    
    // 모든 병렬 작업 후 완료
    await dispatch('completeBatch', { 
      itemCount: items.length,
      batchId,
      mode: 'parallel'
    });
  };
  
  return (
    <div>
      <button onClick={() => handleBatchOperation(items)}>
        순차 배치
      </button>
      <button onClick={() => handleParallelBatch(items)}>
        병렬 배치
      </button>
    </div>
  );
}

디스패치에서의 오류 처리

typescript
function ErrorHandlingDispatcher() {
  const { dispatchWithResult } = useActionDispatchWithResult();
  
  const handleRiskyOperation = async () => {
    try {
      const result = await dispatchWithResult('riskyOperation', 
        { data: 'sensitive-data' },
        { 
          result: { collect: true },
          timeout: 5000
        }
      );
      
      if (result.aborted) {
        console.log('작업이 중단되었습니다:', result.abortReason);
        // 중단을 우아하게 처리
      } else if (result.terminated) {
        console.log('작업 시간 초과');
        // 타임아웃 처리
      } else if (result.success) {
        console.log('작업 완료:', result.results);
        // 성공 처리
      } else {
        console.log('작업이 오류와 함께 실패');
        // 일반적인 실패 처리
      }
      
    } catch (error) {
      console.error('디스패치 실패:', error);
      // 디스패치 수준 오류 처리
    }
  };
  
  return <button onClick={handleRiskyOperation}>위험한 작업</button>;
}

디스패치 결과 구조

성공 결과

typescript
interface DispatchResult<T = any> {
  success: true;
  aborted: false;
  terminated: false;
  results: T[];           // 모든 핸들러 반환값
  errors: never[];        // 성공한 작업에 대해서는 비어있음
  execution: {
    handlersExecuted: number;
    startTime: number;
    endTime: number;
    duration: number;
    mode: 'sequential' | 'parallel' | 'race';
  };
  metadata?: {
    handlerIds: string[];
    priorities: number[];
    payload: any;           // 최종 페이로드 상태
  };
}

중단된 결과

typescript
interface AbortedResult {
  success: false;
  aborted: true;
  terminated: false;
  abortReason: string;     // controller.abort()에 제공된 이유
  abortedBy: string;       // 중단을 일으킨 핸들러 ID
  results: any[];          // 중단 전에 실행된 핸들러의 결과
  errors: Error[];
  execution: {
    handlersExecuted: number;
    startTime: number;
    endTime: number;
    duration: number;
  };
}

타임아웃 결과

typescript
interface TimeoutResult {
  success: false;
  aborted: false;
  terminated: true;
  timeoutMs: number;       // 구성된 타임아웃 값
  results: any[];          // 타임아웃 전의 부분 결과
  errors: Error[];
  execution: {
    handlersExecuted: number;
    startTime: number;
    endTime: number;
    duration: number;
  };
}

성능 최적화

효율적인 디스패치

typescript
// ✅ 좋음 - 액션 레지스터 인스턴스 재사용
const register = new ActionRegister<MyActions>();

function OptimizedComponent() {
  const handleMultipleActions = async () => {
    // 여러 디스패치에 동일한 레지스터 재사용
    await register.dispatch('action1', { data: 'a' });
    await register.dispatch('action2', { data: 'b' });
    await register.dispatch('action3', { data: 'c' });
  };
}

// ❌ 피해야 할 것 - 반복적으로 새 레지스터 생성
function InefficientComponent() {
  const handleAction = async () => {
    const newRegister = new ActionRegister<MyActions>(); // 비용이 많이 듦!
    await newRegister.dispatch('action', { data: 'test' });
  };
}

배치 결과 수집

typescript
function BatchResultCollector() {
  const { dispatchWithResult } = useActionDispatchWithResult();
  
  const handleBatchWithResults = async (operations: Operation[]) => {
    const results = await Promise.all(
      operations.map(op => 
        dispatchWithResult('processOperation', 
          { operation: op },
          { result: { collect: true } }
        )
      )
    );
    
    // 배치 결과 분석
    const successful = results.filter(r => r.success);
    const failed = results.filter(r => !r.success);
    
    console.log(`배치 완료: ${successful.length} 성공, ${failed.length} 실패`);
    
    return {
      successful: successful.length,
      failed: failed.length,
      results: results
    };
  };
  
  return <button onClick={() => handleBatchWithResults(operations)}>
    배치 처리
  </button>;
}

사용 사례별 디스패치 패턴

1. 발사 후 망각 (분석)

typescript
// 결과가 필요하지 않음, 단순히 트리거
await dispatch('trackEvent', { event: 'button_click', data: eventData });

2. 검증 파이프라인 (결과 수집)

typescript
// 검증이 통과했는지 알아야 함
const result = await dispatchWithResult('validateData', 
  { data: formData },
  { result: { collect: true } }
);

if (result.success) {
  proceedWithValidData();
} else {
  showValidationErrors(result.errors);
}

3. 다단계 작업 (진행 상황 추적)

typescript
// 복합 작업을 통한 진행 상황 추적
const result = await dispatchWithResult('complexOperation', 
  { operation: 'data-migration' },
  { 
    result: { 
      collect: true,
      includeMetadata: true 
    }
  }
);

// 사용자에게 진행 상황 표시
result.results.forEach(step => {
  console.log(`단계 ${step.step}: ${step.success ? '✅' : '❌'}`);
});

실제 예제: 디스패치를 사용한 결과 수집

UseActionWithResult 데모에서 포괄적인 디스패치 패턴을 확인하세요:

typescript
// 결과 수집과 함께 순차 워크플로우 실행
const executeWorkflow = async () => {
  const result = await dispatchWithResult('processWorkflow', 
    { data: workflowData },
    { result: { collect: true } }
  );
  
  // 다양한 디스패치 결과 처리
  if (result.success) {
    setWorkflowResults(result.results);
  } else if (result.aborted) {
    setError(`워크플로우 중단: ${result.abortReason}`);
  }
};

이 예제는 포괄적인 오류 처리와 UI 통합을 가진 dispatchWithResult의 실제 사용을 보여줍니다.

관련 문서

Released under the Apache-2.0 License.