Dev_logcomma

Vue 다중 이미지 업로드 구현 – COMMA #1

2025-03-26
ProjectVue
다중 이미지 구현

💬 구성 흐름

파일 선택 → 미리보기 생성 → 상위 컴포넌트로 데이터 전달 → 서버 업로드 준비 → 업로드


🔎 컴포넌트 구조

📍 PostEditImg (부모)

  • 다중 이미지 업로드 UI와 로직 담당
  • 최대 4개의 이미지 슬롯 관리
  • 업로드 트리거 및 미리보기 생성

📍 PostEditImgCard (자식)

  • 개별 이미지 카드 렌더링
  • 데이터 존재 여부에 따라 미리보기 or 업로드 버튼 표시
  • size / opacity 등 props 기반 스타일링

input[type="file"]는 숨기고 카드 클릭으로 input을 트리거하는 방식


🔗 임시 URL 생성

  • URL.createObjectURL(file) → 브라우저 미리보기 URL 생성
  • <img :src="preview"> 바로 표현 가능

✔ 메모리 해제 필요

  • createObjectURL은 자동 해제되지 않음
  • 사용 후 URL.revokeObjectURL(url) 호출 필수

수정 전

js
1
handleFileChange(event, index) {
  const files = event.target.files;
  if (!files || files.length === 0) return;
 
  const newImages = Array.from(files).map((file) => ({
    file,
    preview: URL.createObjectURL(file),
  }));
 
  this.$emit("addImage", { index, images: newImages });
  event.target.value = "";
}

수정 후

js
1
handleFileChange(event, index) {
  const files = event.target.files;
  if (!files || files.length === 0) return;
 
  if (this.images[index] && this.images[index].preview) {
    URL.revokeObjectURL(this.images[index].preview);
  }
 
  const newImages = Array.from(files).map((file) => ({
    file,
    preview: URL.createObjectURL(file),
  }));
 
  this.$emit("addImage", { index, images: newImages });
  event.target.value = "";
}

💬 코멘트

미리보기 구현은 쉬운데, 메모리 해제를 고려하지 않으면 장시간 사용 시 누수 발생 가능


🚨 이미지 추가 시 슬롯 위치 벗어남

슬롯 위치 오류

문제 상황

이미지 추가 시 0번 슬롯에 위치해야 하지만, 조건 분기 불명확으로 인해 예상치 못한 레이아웃이 발생

관련 코드

vue
1
<post-edit-img-card
  v-if="images.length > 0"
  :imgSrc="images[0].preview || images[0]"
  :size="'w-[400px] h-[400px] lg:w-[440px] lg:h-[440px]'"
  :opacity="100"
  @click="handleRemoveImage(0)"
/>

💡 원인 분석

  • Vue는 Virtual DOM 기반으로 컴포넌트 재사용을 시도함
  • v-if만 존재하고 v-else가 없으면 상태 변화 시 렌더링 우선순위가 흔들릴 수 있음
  • 커스텀 컴포넌트 재사용 시 슬롯 위치가 어긋나는 문제 발생

✅ 해결 방법

1. v-else 명시

vue
1
<post-edit-img-card
  v-if="images.length > 0"
  :imgSrc="images[0].preview"
  @click="handleRemoveImage(0)"
/>
<post-edit-img-card
  v-else
  @click="triggerFileSelect(0)"
/>

분기 조건이 명확해져서 Virtual DOM이 정상적으로 슬롯을 유지


2. key 고정값 부여

vue
1
<post-edit-img-card
  v-if="images.length > 0"
  :key="'image-' + images[0].preview"
  :imgSrc="images[0].preview"
/>

key는 Vue가 컴포넌트를 비교할 때 식별자로 활용됨 → 위치 안정화


📍 최종 구현 패턴

vue
1
<!-- 0번 슬롯 -->
<post-edit-img-card
  v-if="images.length > 0"
  :imgSrc="images[0].preview"
  :size="'w-[400px] h-[400px] lg:w-[440px] lg:h-[440px]'"
  :opacity="100"
  @click="handleRemoveImage(0)"
/>
<post-edit-img-card
  v-else
  @click="triggerFileSelect(0)"
/>

업로드 모드 + 수정 모드 모두 지원 가능하도록 설계


🛠️ 개선 아이디어

  • 드래그 앤 드랍 업로드
  • Remove/Insert 시 애니메이션 추가
  • 용량 및 파일 확장자 유효성 검사
  • Multi 업로드 + Progress 표시
  • 서버 업로드 큐 관리