string[] || null
이었다. 아무리 해도 string 배열이 타입으로 들어가지 않아서 그렇게 진행했지만... image_url을 타입에 넣으니 'never[]' 형식은 'string' 형식에 할당할 수 없습니다.
라는.. 처음보는 타입이 나와서 찾다가 결국 찾지 못해 최대한 쓰지 않으려 했던 any 타입을 쓰게 되었다는... 슬픈 이야기... const [uploadedFileUrl, setUploadedFileUrl]: any = useState([]);
...
return (
<ProductsImage uploadedFileUrl={uploadedFileUrl} setUploadedFileUrl={setUploadedFileUrl} />
interface Props {
uploadedFileUrl: string[],
setUploadedFileUrl: React.Dispatch<React.SetStateAction<string[]>>
}
const ProductsImage = ({uploadedFileUrl, setUploadedFileUrl}: Props) => {
return (
<div>
<div>
<h2>상품이미지*</h2>
<p>0/12</p>
</div>
<div>
<label htmlFor='file'>
<input type='file' id='file' name='file' multiple hidden /> +
</label>
</div>
</div>
)
}
import React, { useState } from 'react'
const [files, setFiles] = useState<File[]>([]);
const handleFiles = async (e: React.ChangeEvent<HTMLInputElement>) => {
const fileList = e.target.files;
if (fileList) {
const filesArray = Array.from(fileList);
filesArray.forEach((file) => {
handleAddImages(file);
});
}
};
코드 추가 설명
1. handleFiles 함수를 만들고 files의 내용을 뽑아내 fileList에 담아준다.
2. fileList가 있다면 Array.from 함수를 사용하여 배열로 만들어준다.
3. 배열을 forEach문으로 순회하여 각각의 파일(file)마다 handleAddImages함수에 넣어준다.
import { supabase } from '../../../api/supabase/supabaseClient';
import { v4 as uuid } from 'uuid';
const handleAddImages = async (file: File) => {
try {
const newFileName = uuid();
const {data, error} = await supabase
.storage
.from('Image')
.upload(`products/${newFileName}`, file)
if(error) {
console.log('파일이 업로드 되지 않습니다.', error);
return;
}
const res = supabase.storage.from('Image').getPublicUrl(data.path);
setFiles((prevFiles) => [file, ...prevFiles]);
setUploadedFileUrl((prev:any) => [...prev, res.data.publicUrl]);
} catch (error) {
console.error('알 수 없는 문제가 발생하였습니다. 다시 시도하여 주십시오.', error);
}
};
코드 추가 설명
1. file은 File 타입으로 지정해주고, try/catch문을 사용한다.
2. supabase storage에는 한글로 된 파일명은 업로드가 되지 않기 때문에 새로운 파일 이름을 주기 위해 newFileName를 선언하여 uuid를 할당시켜준다.
3. supabase storage upload 로직을 적어준 뒤, from에는 내가 설정한 버킷이름을 적고, 업로드할 파일 이름에 백틱을 이용하여업로드되는 폴더명/uuid
를 넣어준다.
4. res 라는 변수를 선언하여 supabase storage의 지정된 버킷에서 원하는 이미지(data)의 path 값을 가져와 할당시켜준다. (그 path값에서 다시 data의 publicUrl이 우리가 원하는 url 문자열)
5. 사진이 업로드 될 때마다 원본파일과 url 모두 기존에 있던 파일을 전개구문으로 펼쳐 새로운 파일을 붙여 새로운 문자열 배열 형태로 만들어준다.
const res = supabase.storage.from('Image').getPublicUrl(data.path);
setFiles((prevFiles) => [...prevFiles, file]);
setUploadedFileUrl(res.data.publicUrl);
return (
<div>
<div>
<h2>상품이미지*</h2>
<p>{uploadedFileUrl.length}/12</p>
</div>
<div>
<label htmlFor='file'>
<input type='file' id='file' name='file'
onChange={handleFiles} multiple hidden />+
</label>
</div>
</div>
};
const image_url = uploadedFileUrl
// input값이 모두 들어있는 새로운 객체 만들어서 supabase insert
const entireProductsPosts = {...textRadioValue, category, agreement, image_url}
const ProductsImage = ({uploadedFileUrl, setUploadedFileUrl}: Props) => {
...(중략)
return (
<div>
<div>
<h2>상품이미지*</h2>
<p>0/12</p>
</div>
<div>
{uploadedFileUrl.map((img:string, i:number) =>
<div key={i}>
<img src={img} alt={`${img}-${i}`} />
</div>
)}
<label htmlFor='file'>
<input type='file' id='file' name='file'
onChange={handleFiles} multiple hidden />+
</label>
</div>
</div>
)
};
object-fit 속성
object-fit 속성은 img, video, object, svg 과 같은 요소의 지정된 너비와 높이를 지정하는 css 속성으로, 오브젝트의 비율을 유지한 채 일정한 크기로 재가공 하는 경우에 유용하다.
- fill: 박스 크기에 맞춰 이미지 크기를 조절하며 박스를 가득 채움. (기본값)
- 종횡비가 일치하지 않으면 이미지가 늘어나거나 줄어들음
- contain: 가로세로 비율을 유지한 채로 사이즈가 조절
- 이미지와 컨테이너 간의 비율이 맞지 않는 경우엔 빈공간이 생김
- cover: 이미지의 종횡비를 유지하면서 박스를 가득 채움.
- 종횡비가 일치하지 않으면 컨테이너 박스를 넘어간 이미지 객체는 잘림
- none: 이미지 크기를 조절하지 않음
- scale-down: none과 contain 중 이미지의 크기가 더 작아지는 값에 따름
- 크기가 다양한 이미지를 목록으로 표시할 때, 아주 작은 이미지들이 늘어나 확대되면 보기 좋지 않아지는 문제를 피할 수 있는 장점
<div>
{uploadedFileUrl.map((img:string, i:number) =>
<div key={i}>
<img src={img} alt={`${img}-${i}`}
style={{objectFit: 'cover',
objectPosition: 'center',
width: '100%', height: '100%'}}/>
</div>
)}
<label htmlFor='file'>
<input type='file' id='file' name='file'
onChange={handleFiles} multiple hidden />+
</label>
</div>
if (uploadedFileUrl.length > 12) uploadedFileUrl.pop();
if (files.length > 12) files.pop();
=
if (uploadedFileUrl.length > 12 && files.length > 12) uploadedFileUrl.pop() && files.pop();
const res = supabase.storage.from('Image').getPublicUrl(data.path);
setFiles((prevFiles) => [...prevFiles, file]);
setUploadedFileUrl((prev:any) => [...prev, res.data.publicUrl]);
const res = supabase.storage.from('Image').getPublicUrl(data.path);
setFiles((prevFiles) => [file, ...prevFiles]);
setUploadedFileUrl((prev:any) => [res.data.publicUrl, ...prev]);
const ProductsImage = ({uploadedFileUrl, setUploadedFileUrl}: Props) => {
...(중략)
return (
<div>
<div>
<h2>상품이미지*</h2>
<p>0/12</p>
</div>
<div>
{uploadedFileUrl.map((img:string, i:number) =>
<div key={i}>
<img src={img} alt={`${img}-${i}`} />
</div>
)}
{uploadedFileUrl.length >= 12 ? <></> :
<label htmlFor='file'>
<input type='file' id='file' name='file'
onChange={handleFiles} multiple hidden />+
</label>}
</div>
</div>
)
};
const ProductsImage = ({uploadedFileUrl, setUploadedFileUrl}: Props) => {
...(중략)
return (
<div>
<div>
<h2>상품이미지*</h2>
<p>{uploadedFileUrl.length}/12</p>
</div>
<div>
{uploadedFileUrl.map(
(img:string, i:number) =>
<div key={i}>
<img src={img} alt={`${img}-${i}`} />
<button> X </button>
</div>
)}
{uploadedFileUrl.length >= 12 ? <></> :
<label htmlFor='file'>
<input type='file' id='file' name='file'
onChange={handleFiles} multiple hidden />+
</label>}
</div>
</div>
)
};
const ProductsImage = ({uploadedFileUrl, setUploadedFileUrl}: Props) => {
...(중략)
const handleDeleteImage = (id:any) => {
setUploadedFileUrl(uploadedFileUrl.filter((_, index) => index !== id));
setFiles(files.filter((_, index) => index !== id));
};
코드 추가 설명
함수에 들어가는id
라는 인자는 변경 state인setUploadedFileUrl
안에서 기존 파일 배열uploadedFileUrl
의 인자index
와 비교하여 다르다면filter
로 걸러낸다. (files도 동일)
return (
<div>
<div>
<h2>상품이미지*</h2>
<p>{uploadedFileUrl.length}/12</p>
</div>
<div>
{uploadedFileUrl.map(
(img:string, i:number) =>
<div key={i}>
<img src={img} alt={`${img}-${i}`} />
<button onClick={() => handleDeleteImage(i)}>X</button>
</div>
)}
{uploadedFileUrl.length >= 12 ? <></> :
<label htmlFor='file'>
<input type='file' id='file' name='file'
onChange={handleFiles} multiple hidden />+
</label>}
</div>
</div>
)
const ProductsCard = ({product}: {product: ProductsPostType}) => {
const { title, price, quality, image_url } = product
...
};
<div>
<div>
<img src={image_url[0]}/>
</div>
<div>
{[quality].map(condition => <li key={condition}>{condition}</li>)}
</div>
<h2>{title}</h2>
<h3>{price}원</h3>
</div>
<div>
<div>
{image_url !== null ? <img src={image_url[0]}/> : <h1></h1>}
</div>
<div>
{[quality].map(condition => <li key={condition}>{condition}</li>)}
</div>
<h2>{title}</h2>
<h3>{price}원</h3>
</div>