JOYLOG

[React] ๋‹ค์ค‘ ์ฒจ๋ถ€ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ฐ ์ œ๊ฑฐ ์ปดํฌ๋„ŒํŠธ ๋ณธ๋ฌธ

D E V E L O P E R ๐Ÿ’ป/Front-end

[React] ๋‹ค์ค‘ ์ฒจ๋ถ€ ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋ฐ ์ œ๊ฑฐ ์ปดํฌ๋„ŒํŠธ

์ฃผํž 2022. 9. 24. 09:56

์ด๋ฏธ์ง€ ์ฒจ๋ถ€ ๊ธฐ๋Šฅ์„ ๊ฐœ๋ฐœํ•˜๋ฉด์„œ,

input ํƒœ๊ทธ์— ํŒŒ์ผ์ด ์ฒจ๋ถ€๋  ๋•Œ(onChange)์˜ event ๋ฅผ ์ฝ˜์†”์— ์ฐ์–ด๋ณด์•˜๋Š”๋ฐ์š”.

 

๋‹ค์ค‘์—…๋กœ๋“œ?

<input type="file" multiple /> ์ฒ˜๋Ÿผ multiple ์†์„ฑ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋ฉด, ๋‹ค์ค‘ ํŒŒ์ผ ์—…๋กœ๋“œ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค๊ณ  ํ•ด์š”!

์ฝ˜์†”์„ ์ฐ์–ด๋ณด๋ฉด Files ๋‚ด๋ถ€์— ํŒŒ์ผ์„ Object ํ˜•ํƒœ๋กœ ๋ฐ›์•„์˜ค๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์–ด์š”.

 

๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„  ์ด๋ฏธ์ง€ DOM์ด ํ•„์š”ํ•œ๋ฐ, ์ด๊ฒƒ์€ URL.createObjectUrl() ๋ฉ”์„œ๋“œ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์š”.

 

์œ„์—์„œ ๋ฐ›์•„์˜จ ํŒŒ์ผ Object ๊ฐ๊ฐ์„ URL.createObjectUrl() ์˜ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„ฃ์–ด์ฃผ๋ฉด ์ฃผ์–ด์ง„ ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” URL์„ DOMString ์œผ๋กœ ๋ฐ˜ํ™˜๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์š”. ์ด ๋•Œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ ๋„ฃ์–ด์ฃผ๋Š” Object๋Š” File, Blob, MediaSource ํ˜•ํƒœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.

 

์ฐธ๊ณ  : MDN Web Docs : URL.createObjectURL()

 

URL.createObjectURL() - Web API | MDN

URL.createObjectURL() ์ •์  ๋ฉ”์„œ๋“œ๋Š” ์ฃผ์–ด์ง„ ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” URL์„ DOMString์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น URL์€ ์ž์‹ ์„ ์ƒ์„ฑํ•œ ์ฐฝ์˜ document๊ฐ€ ์‚ฌ๋ผ์ง€๋ฉด ํ•จ๊ป˜ ๋ฌดํšจํ™”๋ฉ๋‹ˆ๋‹ค.

developer.mozilla.org

 

์œ„์—์„œ ์–ป์€ DOMString์„ <img> src ์†์„ฑ์— ๋Œ€์ž…ํ•ด์ค€๋‹ค๋ฉด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐Ÿ‘

 


 

์š”์•ฝ

1๏ธโƒฃ <input> ๋Œ€์‹  <label> ์„ ์‚ฌ์šฉํ•˜์—ฌ, ์ด๋ฏธ์ง€ ์ฒจ๋ถ€ UI๋ฅผ ๋””์ž์ธํ•œ๋‹ค. ์ด ๋•Œ, <input>์€ display : none ์œผ๋กœ ์ˆจ๊ธด๋‹ค.

2๏ธโƒฃ <input> ์— onChange ์ด๋ฒคํŠธ๋ฅผ ๋“ฑ๋กํ•œ๋‹ค.

3๏ธโƒฃ onChange ์ด๋ฒคํŠธ์— ์ฒจ๋ถ€๋œ ํŒŒ์ผ์„ DOMString ์œผ๋กœ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜๋ฅผ ๋Œ€์ž…ํ•œ๋‹ค.

4๏ธโƒฃ ๋ฐ˜ํ™˜๋ฐ›์€ DOMString์€ ๋ฐฐ์—ด ํ˜•ํƒœ์˜ State ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.

5๏ธโƒฃ ์ฒจ๋ถ€๋œ ์ด๋ฏธ์ง€ DOMString์„ ๋‹ด๊ณ  ์žˆ๋Š” ๋ฐฐ์—ด State๋ฅผ ์ˆœํ™˜ํ•˜์—ฌ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•ด์ค€๋‹ค.

 

 


 

State ์„ ์–ธ

const [previewImgList, setPreviewImgList] = useState([])
  • ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ๊ฐ์ฒด๋“ค์„ ๊ด€๋ฆฌํ•  ๋ฐฐ์—ด ํ˜•ํƒœ์˜ State๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค.

 

๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ DOMString ์ €์žฅ

const handleCreatePreviewImg = event => {
  let previewUrlList = [...previewImgList]
  const uploadFilesObject = event.target.files
  for (let i = 0; i < uploadFilesObject.length; i++) {
    const currentImgUrl = URL.createObjectURL(uploadFilesObject[i])
    previewUrlList.push(currentImgUrl)
  }
  // ์ด๋ฏธ์ง€ ์ตœ๋Œ€ 5๊ฐœ ์ œํ•œ
  if (previewUrlList.length > 5) {
    previewUrlList = previewUrlList.slice(0, 5)
  }
  setPreviewImgList(previewUrlList)
  // (+ ๊ฐœ๋ฐœ ์ค‘์ธ ํ”„๋กœ์ ํŠธ์— ๋”ฐ๋ผ ์„œ๋ฒ„์— ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ.)
}
  • onChange ์ด๋ฒคํŠธ์— ๋„ฃ์–ด์ค„ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  • ์ฒจ๋ถ€ ํŒŒ์ผ์ด ๋“ค์–ด์˜ฌ ๋•Œ, ๋“ค์–ด์˜จ ํŒŒ์ผ๋“ค์„ DOMString ํ˜•ํƒœ๋กœ ๋ฐ˜ํ™˜ํ•˜์—ฌ ์ค€๋‹ค. ๋ฐ˜ํ™˜๋œ ํŒŒ์ผ์€ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋Š” state์— ์ถ”๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.
  • ์ด๋ฏธ์ง€๋Š” ์ตœ๋Œ€ 5๊ฐœ ์ฒจ๋ถ€ํ•  ์ˆ˜ ์žˆ๋„๋ก, ๋ฐฐ์—ด์„ ์ž๋ฅด๋Š” ๋ฐฉ์‹์œผ๋กœ ์ œํ•œํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์‚ญ์ œ

const handleDeleteImage = id => {
  const filteredImgList = []
  ;[...previewImgList].map((img, imgIndex) => {
    if (id !== imgIndex) {
      filteredImgList.push(img)
    } else {
      URL.revokeObjectURL(img)
    }
  })
  setPreviewImgList(filteredImgList)
  setValue('urls', filteredImgList)
}
  • handleDeleteImage๋Š” ์‚ญ์ œํ•˜๊ณ ์ž ํ•˜๋Š” ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ ์œ„ X ๋ฒ„ํŠผ์„ onClick ํ•˜๋ฉด ์ž‘๋™ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
  • ๋ฏธ๋ฆฌ๋ณด๊ธฐ DOMString ๊ฐ’์ด ์ €์žฅ๋œ ๋ฐฐ์—ด์„ ์ˆœํ™˜ํ•˜๋ฉฐ, ํ˜„์žฌ ๋ˆ„๋ฅธ ์ปดํฌ๋„ŒํŠธ์˜ id๊ฐ’๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€์˜ id๊ฐ’์ด ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด (์‚ญ์ œํ•˜๊ณ ์žํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ), ๋ฐฐ์—ด์— ํฌํ•จํ•ฉ๋‹ˆ๋‹ค.
  • ๋งŒ์•ฝ ์ผ์น˜ํ•œ๋‹ค๋ฉด (์‚ญ์ œํ•˜๊ณ ์žํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ธ ๊ฒฝ์šฐ), ๋ฐฐ์—ด์— ํฌํ•จํ•˜์ง€ ์•Š์œผ๋ฉฐ URL.revokeObjectURL ๋ฅผ ํ†ตํ•ด URL ๊ฐ์ฒด๋ฅผ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. → MDN์—์„œ ์ตœ์ ์˜ ์„ฑ๋Šฅ๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์œ„ํ•ด์„œ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค!

 

์ฃผ์š” ์ฝ”๋“œ

...
const UploadPreviewImage: React.FC<TProps> = ({ ... }) => {
  ...
  return (
    <StyledWrapper>
      <div className="photo-input-preview-box">
        <div className="photo-wrap">
          <label htmlFor="uploadFile">
              <div>์‚ฌ์ง„ ์—…๋กœ๋“œ</div>
          </label>
        </div>
        {previewImgList.map((previewImg, previewImgIdx) => {
          return (
            <ReviewPreviewPhoto
              key={previewImgIdx}
              id={previewImgIdx}
              imgSrc={previewImg}
              handleDeleteImage={handleDeleteImage}			
            />
          );
        })}
      </div>
      <input
        type="file"
        multiple
        id="uploadFile"
        accept="image/*"
        style={{ display: 'none' }}
        onChange={handleCreatePreviewImg}
      />
    </StyledWrapper>
  );
};

export default UploadPreviewImage;
  • input ๋Œ€์‹  label ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด input UI๋ฅผ ๋””์ž์ธํ•ฉ๋‹ˆ๋‹ค.
  • previewImgList (= DOMString์„ ๋‹ด๊ณ  ์žˆ๋Š” ๋ฐฐ์—ด)์„ ์ˆœํ™˜ํ•˜๋ฉฐ ์ด๋ฏธ์ง€๋ฅผ ๋žœ๋”๋งํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฏธ์ง€์— handleDeleteImage ํ•จ์ˆ˜๋ฅผ props ๋กœ ๋„˜๊ฒจ์ค˜, ์ปดํฌ๋„ŒํŠธ ์œ„์— X ๋ฒ„ํŠผ์„ ๋‘๊ณ , X๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ์ž‘๋™ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
Comments