상품 관리 - Part6 상품 상세 이미지 슬라이드 구현
ㆍProject Diary/React + Firebase (Snack ShoppingMall)
Firebase에 여러 개의 파일을 저장하는 방법 기록
이 작업의 주요 포인트는 Firebase에 여러 개의 파일을 저장하고, 저장된 파일들을 받아와서 슬라이드로 보여주는 것입니다. 따라서 이 부분에 중점을 두고 구현 과정을 기록하겠습니다.
파일 업로드
1개의 파일(대표 사진)과 여러개의 파일(상세 사진)을 Firebase Storage에 업로드하는 방식의 차이점에 대해서 설명하겠습니다.
> 1개의 파일 업로드 (대표 사진 업로드)
대표 사진은 단일 파일이기 때문에, 파일을 선택하고 Storage에 업로드한 후, 해당 파일의 URL을 받아오는 과정이 간단합니다.
// 대표 사진 변경 처리
const handleFileChange = (e) => {
const file = e.target.files[0];
setProduct((prevProduct) => ({ ...prevProduct, photo: file }));
setPhotoValue(e.target.value);
};
// 대표 사진 업로드
if (product.photo) {
const fileRef = storageRef.child(product.photo.name);
await fileRef.put(product.photo);
addProduct.photo = await fileRef.getDownloadURL();
}
- 파일 선택 후 photo 상태에 파일 객체 저장.
- Firebase Storage에 파일을 업로드.
- 업로드된 파일의 URL을 받아서 addProduct.photo에 저장.
> 여러개의 파일 (상세 사진 업로드)
여러 개의 파일을 업로드할 때는 배열로 관리하고, 각 파일을 순차적으로 업로드하여 URL을 받아옵니다.
// 상세 사진 변경 처리 (여러 개 파일)
const handleDetailFileChange = (e) => {
const files = e.target.files;
setDetailPhotos(Array.from(files));
};
// 상세 사진 업로드
if (detailPhotos.length > 0) {
const detailPhotoURLs = [];
await Promise.all(
detailPhotos.map(async (file, index) => {
const fileName = `detailPhoto${index + 1}_${Date.now()}_${file.name}`;
const detailFileRef = storageRef.child(fileName);
await detailFileRef.put(file);
detailPhotoURLs.push(await detailFileRef.getDownloadURL());
})
);
addProduct.detailPhotos = detailPhotoURLs;
}
- 여러 파일을 선택 후 detailPhotos 상태에 파일 객체 배열 저장.
- 배열의 각 파일을 순차적으로 Firebase Storage에 업로드.
- 업로드된 각 파일의 URL을 배열에 저장하고, addProduct.detailPhotos에 저장.
데이터패치, 슬라이더 동기화
업로드한 파일을 패치할 때도 대표 사진과 상세 사진을 각각 처리합니다.
import React, { useState, useEffect, useRef } from "react";
import styled from "styled-components";
import { useLocation } from "react-router-dom";
import Slider from "react-slick";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { IoIosArrowDropleftCircle, IoIosArrowDroprightCircle } from "react-icons/io";
// 스타일 생략
const ProductDetailSection = () => {
const location = useLocation();
const { item } = location.state; // 상품 데이터 받아오기
const [nav, setNav] = useState(null);
const [subNav, setSubNav] = useState(null);
let sliderRef = useRef(null);
let subSliderRef = useRef(null);
useEffect(() => {
setNav(sliderRef.current);
setSubNav(subSliderRef.current);
}, []);
return (
<ProductDetailSectionBlock>
<h2 style={{ color: "#333", marginBottom: "20px", fontSize: "30px" }}>
{item.name}
</h2>
<div className="content">
<div className="sliderContainer">
{/* 메인 슬라이더 */}
<Slider
className="slide"
asNavFor={subNav}
ref={sliderRef}
arrows={false}
fade={true}
>
{item.detailPhotos.map((photo, index) => (
<div key={index}>
<img src={photo} alt={`detail ${index}`} />
</div>
))}
</Slider>
{/* 서브 슬라이더 */}
<Slider
className="subSlide"
asNavFor={nav}
ref={subSliderRef}
slidesToShow={3}
slidesToScroll={1}
dots={false}
centerMode={true}
focusOnSelect={true}
infinite={true}
draggable={false}
prevArrow={<IoIosArrowDropleftCircle />}
nextArrow={<IoIosArrowDroprightCircle />}
>
{item.detailPhotos.map((photo, index) => (
<div key={index}>
<img src={photo} alt={`thumbnail ${index}`} />
</div>
))}
</Slider>
</div>
<div className="info">
<p>가격: {parseInt(item.price).toLocaleString()}¥</p>
<p>{item.description}</p>
</div>
</div>
</ProductDetailSectionBlock>
);
};
export default ProductDetailSection;
- 데이터 패치 : 상품 상세 페이지에서 대표 사진과 상세 사진의 URL을 이용하여 이미지를 표시, Slider 컴포넌트를 이용하여 메인 슬라이더와 썸네일 슬라이더 구현.
- 슬라이더 동기화 : useRef와 useEffect를 이용하여 메인 슬라이더와 썸네일 슬라이더를 동기화.