
구성 흐름 :
파일 선택 → 미리보기 생성 → 상위 컴포넌트로 데이터 전달 → 서버 업로드 준비 → 업로드
PostEditImg: 다중 이미지 업로드 UI와 로직을 담당하는 부모 컴포넌트
PostEditImgCard: 개별 이미지 카드를 렌더링하는 자식 컴포넌트
🎨 이미지 업로드 UI
<input type="file" multiple>를 4개 생성하여 각 슬롯에 연결v-for를 사용해 인덱스 별로 파일 입력을 동적으로 생성hidden클래스로 input을 숨기고,PostEditImgCard클릭 시triggerFileSelect로 파일 선택을 트리거- 이미지 업로드 후 미리보기는
URL.createObjectURl(file)로 생성
URL.createObjectURL(file) 은 파일 객체를 가리키는 임시 URL을 생성해준다. 이 URLdmf <img> 태그의 src 속성에 설정하면 사용자가 업로드한 이미지를 화면에 미리보기로 보여줄 수 있다.createObjectURL 로 생성된 URL은 브라우저가 자동으로 해제하지 않기 때문에, 불필요한 메모리 사용을 방지하려면 사용이 끝난 후 URL.revokeObjectURL(url)을 호출하여 해제해야 한다.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 = "";
},
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 = "";
},
💬 코멘트
미리보기 구현에만 집중하다 보니, 이후의 처리를 충분히 고려하지 못했던 것 같다. 그래서
URL.createObjectURL을 적용했지만, 메모리 누수를 방지하기 위해URL.revokeObjectURL을 사용해야 한다는 점을 간과했다. 앞으로는 이러한 후처리까지 신중하게 고려하며 개발해야겠다.

🚨 문제점
이미지를 추가하면 의도된 슬롯에 위치해야 하지만, 제대로 렌더링 되지 않고, 레이아웃을 벗어나 예상치 못한 위치에 렌더링 되는 문제 발생
<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)"
></post-edit-img-card>
Virtual DOM 이라는 가상 화면을 사용한다. 이 가상 화면은 실제 HTML을 미리 계산해서 효율적으로 업데이트 한다.v-if만 있을 때는, 조건이 거짓일 때 그 부분이 없어지는데, vue는 혼란스러울 수 있다.postEditImgCard 는 같은 컴포넌트를 재사용하기 때문에 이 부분에서 혼란이 생길 수 있다.💡 문제 분석
images.length > 0일 때 0번 슬롯에 미리보기를 보여주고 클릭 시 해당 이미지를 삭제하게 조건부 처리를 해둔 상태- 하지만
images.length > 0이 거짓일 때 어떻게 할지 명확하게 지정되어 있지 않고 있다.- 결과적으로 이미지를 추가하면, 0번 슬롯에 미리보기가 들어가야 하지만 Vue가 레이아웃을 잘못 재정렬해서 다른 곳에 배치되게 만들었다는 가정
<post-edit-img-card
v-else
@click="triggerFileSelect(0)"
></post-edit-img-card>
✅ v-else 를 추가
v-else를 추가하여images.length > 0이 거짓일 때 렌더링 할 조건을 명확하게 함v-else가 없었을 때는 갱신 과정이 불안정해서 0번 슬롯이 레이아웃을 벗어났지만, 규칙이 제대로 잡히니 깔끔하게 갱신됨
🧐 다른 해결법 : key 속성으로 위치 고정
key속성을 주어서, Vue가 같은 컴포넌트를 새로 그리더라도 각각을 구분할 수 있게 함<post-edit-img-card v-if="images.length > 0" :key="'image-' + images[0].preview" :imgSrc="images[0].preview" </post-edit-img-card>
<!-- 이미지 영역 -->
<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)"
></post-edit-img-card>
@click 이벤트를 추가하여 클릭 시 해당 이미지를 삭제할 수 있게 했다.<post-edit-img-card
v-else
@click="triggerFileSelect(0)"
></post-edit-img-card>
🛠️ 추가 개선점
- 드래그 앤 드랍 기능 추가 : 이미지 업로드 UX 개선
- 이미지 추가/삭제 시 애니메이션 추가 : 부드러운 전환
- 파일 크기 유효성 검사 : 용량이 클 경우에 대한 검증 부재