이 글은 ImageCompressor 컴포넌트를 기준으로 작성한 구현 기록입니다. 단일 파일 예제가 아니라, 사용자가 여러 이미지를 한 번에 선택했을 때 화면 상태가 어떻게 움직여야 하는지에 집중합니다.

문제: 여러 파일을 하나의 로딩 값으로 다루면 화면이 모호해진다

이미지 압축 도구에서 가장 단순한 구현은 isLoading 하나를 두고, 처리 중이면 모든 버튼을 막는 방식입니다. 단일 파일 도구라면 이 정도로도 충분할 수 있습니다. 하지만 여러 이미지를 순서대로 압축하고, 완료된 항목은 바로 다운로드할 수 있게 만들려면 상태를 더 세밀하게 나눠야 합니다.

img-compressor는 파일 선택 후 처리 중인 단계와 처리 완료 후 결과 목록을 분리합니다. 압축 중에는 진행률을 보여 주고, 결과가 만들어진 파일만 카드로 추가합니다. 사용자는 아직 처리 중인 파일과 다운로드 가능한 파일을 구분할 수 있어야 하기 때문입니다. 이 구분이 없으면 화면은 멈춘 것처럼 보이고, 버튼을 눌러도 되는지 판단하기 어렵습니다.

진행 상태는 현재 번호와 전체 개수를 함께 가진다

컴포넌트는 여러 파일을 반복하면서 현재 처리 번호와 전체 개수를 상태로 저장합니다. 단순한 퍼센트보다 이 방식이 나은 이유는 사용자가 몇 번째 파일에서 시간이 걸리는지 바로 알 수 있기 때문입니다. 특히 큰 사진과 작은 아이콘이 섞여 있으면 파일별 처리 시간이 다르게 느껴집니다.

setProcessingProgress({
  current: i + 1,
  total: imageFiles.length,
  type: 'compressing',
});

type 값도 함께 둔 이유는 같은 진행 영역에서 압축과 WebP 일괄 변환을 구분하기 위해서입니다. 사용자가 보는 메시지는 비슷해도 내부 작업의 의미가 다릅니다. 압축 중에는 원본 파일을 결과 파일로 만드는 단계이고, WebP 변환 중에는 이미 만들어진 결과를 다른 포맷으로 다시 만드는 단계입니다.

결과 목록은 완료된 파일의 기록이다

파일 처리 함수가 성공하면 결과 객체에는 원본 이름, 원본 크기, 출력 파일, 출력 크기, 미리보기 URL, 감소율 같은 값이 들어갑니다. 이 값들은 화면 표시뿐 아니라 다운로드 파일명을 만들 때도 사용됩니다. 그래서 결과 목록은 단순한 UI 배열이 아니라, 사용자가 실제로 받을 파일의 기록입니다.

이 구조에서는 실패한 파일을 같은 배열에 조용히 넣지 않는 것이 중요합니다. 처리하지 못한 파일이 결과 목록에 들어가면 사용자는 다운로드 버튼을 눌렀을 때 빈 파일이나 잘못된 파일을 받을 수 있습니다. 실패는 결과가 아니라 별도 오류 안내로 남겨야 합니다. 현재 구현은 실패 시 콘솔과 화면 메시지를 통해 흐름을 끊고, 성공한 파일만 결과 카드에 추가하는 방향을 취합니다.

개별 WebP 변환 상태는 카드 단위로 잠근다

압축이 끝난 뒤 사용자는 특정 파일만 WebP로 변환할 수 있습니다. 이때 전체 화면을 다시 로딩 상태로 바꾸면 다른 파일 다운로드까지 막히게 됩니다. 그래서 개별 변환 중인 항목은 파일 식별자에 가까운 값으로 따로 관리합니다. 사용자는 한 카드의 버튼이 변환 중이어도 이미 끝난 다른 파일은 받을 수 있습니다.

const [convertingWebP, setConvertingWebP] = useState(null);

const handleConvertOne = async (image) => {
  setConvertingWebP(image.id);
  try {
    const webpFile = await convertToWebPFile(image.file, quality);
    // 해당 카드에만 WebP 결과를 붙인다.
  } finally {
    setConvertingWebP(null);
  }
};

이 패턴은 사용자가 작업 범위를 이해하게 해 줍니다. 전체 작업인지, 특정 카드 작업인지, 이미 완료된 결과인지가 상태 이름과 UI 잠금 범위에 그대로 드러납니다.

일괄 다운로드는 완료 목록을 기준으로 한다

일괄 다운로드는 현재 결과 목록에 있는 파일만 대상으로 삼습니다. 처리 중인 파일을 예상해서 포함하지 않고, 완료된 결과만 다운로드합니다. 이 기준은 단순하지만 안전합니다. 아직 만들어지지 않은 파일에 대한 링크를 생성하지 않으므로 브라우저 다운로드 흐름이 예측 가능합니다.

브라우저는 여러 다운로드를 짧은 시간에 연속 실행할 때 사용자 설정이나 보안 정책의 영향을 받을 수 있습니다. 그래서 구현에서는 각 다운로드 호출 사이에 약간의 간격을 둘 수 있습니다. 이 처리는 성능 최적화라기보다 브라우저 동작을 더 안정적으로 만드는 사용자 경험상의 선택입니다.

상태 설계 체크리스트

  • 파일 선택 전, 압축 중, 압축 완료 상태가 화면에서 구분되는지 확인합니다.
  • 여러 파일 처리 중 현재 파일 번호와 전체 개수가 사용자에게 보이는지 확인합니다.
  • 실패한 파일이 성공 결과 목록에 섞이지 않는지 확인합니다.
  • 특정 카드의 WebP 변환이 전체 다운로드 버튼을 불필요하게 막지 않는지 확인합니다.
  • 전체 삭제 후 Object URL을 정리하고, 새 파일 처리에 이전 결과가 남지 않는지 확인합니다.

이 구현의 한계

현재 방식은 순차 처리를 기준으로 설명합니다. 대량 파일을 더 빠르게 처리하려면 병렬 처리나 작업 큐를 고려할 수 있지만, 그때는 메모리 사용량, 취소 처리, 실패한 파일만 재시도하는 흐름을 함께 설계해야 합니다. 단순히 동시에 많이 돌리는 것이 항상 좋은 답은 아닙니다.

또한 진행률은 파일 개수 기준이지 실제 처리 시간 기준이 아닙니다. 10개 중 5개를 처리했다고 해서 시간의 절반이 지났다는 뜻은 아닙니다. 그래서 화면 문구도 정밀한 시간 예측처럼 보이지 않게 유지하는 것이 좋습니다.

원본 코드