-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feat/#351] 공연 등록하기 상세 이미지 클라 구현 #355
Changes from all commits
06d5a04
01800d4
e8bda9c
16cbd64
47337d8
eba241b
31d18ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { ChangeEvent, useEffect, useState } from "react"; | ||
import * as S from "../Register.styled"; | ||
import { IconCamera } from "@assets/svgs"; | ||
import Spacing from "@components/commons/spacing/Spacing"; | ||
import useModal from "./../../../hooks/useModal"; | ||
|
||
interface DetailImageProps { | ||
value?: PreviewImageList[] | undefined; | ||
onImagesUpload: (detailImage: PreviewImageList[]) => void; | ||
} | ||
|
||
interface PreviewImageList { | ||
id: number; | ||
performanceImage: string; | ||
} | ||
|
||
const DetailImage = ({ value, onImagesUpload }: DetailImageProps) => { | ||
const { openAlert } = useModal(); | ||
const [previewImgs, setPreviewImgs] = useState<PreviewImageList[] | null>(value || null); | ||
const [inputKey, setInputKey] = useState<number>(Date.now()); | ||
|
||
useEffect(() => { | ||
setPreviewImgs(value || null); | ||
}, [value]); | ||
|
||
const uploadFile = (e: ChangeEvent<HTMLInputElement>) => { | ||
const files = e.target.files; | ||
if (files && files.length > 0) { | ||
// 최대 5장 업로드 안내 | ||
if (previewImgs.length + files.length > 5) { | ||
openAlert({ | ||
title: "가능한 이미지 수를 초과했습니다.", | ||
okText: "확인", | ||
}); | ||
return; | ||
} | ||
|
||
// 파일 순서대로 처리하기 위해 비동기 작업 | ||
const processFile = (file: File) => { | ||
return new Promise<PreviewImageList>((resolve) => { | ||
const fileReader = new FileReader(); | ||
fileReader.onload = function (event) { | ||
const imageUrl = event.target?.result as string; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) onload 함수의 경우 파일을 바탕으로readAsDataURL 메서드를 자체적으로 호출한 뒤 Base64 인코딩 문자열의 형태로 result 형태로 반환한다더라구요! as string 잘 쓴 걸 보면 알 것 같긴 한데 혹시 모르실까봐 참고하시라고 코멘트합니다 ~ |
||
resolve({ | ||
id: Date.now() + Math.floor(Math.random() * 1000), // ctrl 키로 동시에 이미지 선택해도 id 중복되지 않도록 랜덤 값 추가 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 와웅 id idx로 주는 게 좋지 않은 방법이라고 해서 항상 id 값 줄 때 고민이 많았는데 이런 방법도 있군요!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 👍
Comment on lines
+38
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 오옹 이런 작업을!!! 최고채현!! |
||
performanceImage: imageUrl, | ||
}); | ||
}; | ||
fileReader.readAsDataURL(file); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 아! 그리고 readAsDataURL 메서드의 경우에는 파일리더를 통해 파일을 비동기적으로 읽고, 읽기가 성공하면 onload 이벤트가 발생한다고 하네요. 이런 연관성도 알아두면 재밌을 것 같아요 ~ |
||
}); | ||
}; | ||
|
||
// 모든 파일 처리 완료 후 상태 업데이트 | ||
const processAllFiles = async () => { | ||
const newPreviewImgs = await Promise.all( | ||
Array.from(files).map((file) => processFile(file)) | ||
); | ||
const updatedPreviewImgs = [...previewImgs, ...newPreviewImgs]; | ||
onImagesUpload(updatedPreviewImgs); | ||
}; | ||
|
||
processAllFiles(); | ||
} | ||
}; | ||
|
||
const removeImage = (id: number) => { | ||
onImagesUpload(previewImgs.filter((detail) => detail.id !== id)); // 비동기적으로 반영되는 setState때문에 내부에 넣어야 필터링된 최신 이미지들을 바로 반영됨 | ||
|
||
setInputKey(Date.now()); | ||
}; | ||
|
||
return ( | ||
<S.InputRegisterBox $marginBottom={2.8}> | ||
<S.InputTitle>공연 상세 이미지</S.InputTitle> | ||
<S.InputDescription>선택 사항입니다. (최대 5장)</S.InputDescription> | ||
<Spacing marginBottom="1.4" /> | ||
<S.FilesInputWrapper> | ||
<S.HiddenFileInput | ||
key={inputKey} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) inputKey 어디서 쓰나 계속 봤는데 여기서 사용했었네요 ㅎㅎ.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. p5) 저는 inputKey의 역할이 궁금해요! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 코드는 사실 포스터 업로드하는 코드 파일을 복제해서 사용했습니다. 그래서 HiddenFileInput을 그대로 가져와서 조금만 변형시켜서 작성했습니다. 일단, HiddenFileInput의 역할 설명을 어떤 것을 원하시는건지 잘 모르겠습니다..... InputKey는 포스터 등록할때 정의한 것 그대로 사용하고 있습니다! |
||
type="file" | ||
id="files" | ||
accept="image/png, image/jpg, image/jpeg, image/svg" | ||
onChange={uploadFile} | ||
multiple | ||
disabled={previewImgs.length >= 5} | ||
/> | ||
<S.CustomFileInput htmlFor="files" width={15.7} height={21}> | ||
<IconCamera width={"3.2rem"} /> | ||
<S.CustomFileInputCounter> | ||
<S.CustomFileInputLength>{previewImgs.length}</S.CustomFileInputLength>/5 | ||
</S.CustomFileInputCounter> | ||
</S.CustomFileInput> | ||
{previewImgs && | ||
previewImgs.map((previewImg) => ( | ||
<S.PreviewImageWrapper key={previewImg.id} width={15.7} height={21}> | ||
<S.PreviewImage | ||
src={previewImg.performanceImage} | ||
alt={`Preview-${previewImg.id}`} | ||
width={15.7} | ||
height={21} | ||
/> | ||
<S.RemoveImageButton onClick={() => removeImage(previewImg.id)} /> | ||
</S.PreviewImageWrapper> | ||
))} | ||
</S.FilesInputWrapper> | ||
</S.InputRegisterBox> | ||
); | ||
}; | ||
|
||
export default DetailImage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p5) value 속성이 변해서 리렌더링이 발생했을 경우에만, DetailImage의 상태 업데이트 좋네요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p3) 저는 오히려 반대로,,, 이 로직이 필요한 이유가 뭘까요? 제가 지우고 테스트 했는데 정상적으로 동작해서요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헉 그러게요... 그냥 value에 따라 바뀌어야 한다고 생각했는데, 이미지 업로드하기 전에 previewImgs를 직접 바꿔줘서 value가 독단적으로 바뀔일이 없을 것 같습니다...! 이거를 그냥 살리고 뒤에 있는 필요없는 setPreviewImgs를 지우겠습니다.