๐Ÿ  | MultiImageForm ๋ฆฌํŒฉํ† ๋ง: ๋„คํŠธ์›Œํฌ ์˜์กด์„ฑ ์ตœ์†Œํ™”

NewHaยท2025๋…„ 1์›” 8์ผ
post-thumbnail

MultiImageForm์˜ ์ดˆ๊ธฐ ๊ตฌํ˜„์€ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์„ ํƒํ•  ๋•Œ๋งˆ๋‹ค ํ•˜๋‚˜์”ฉ Supabase Storage์˜ temporary ํด๋”์— ์—…๋กœ๋“œํ•˜๊ณ  URL์„ ๊ฐ€์ ธ์˜ค๊ณ , ์‚ญ์ œ ์‹œ์—๋„ ๋งค๋ฒˆ Storage์— ์ฆ‰์‹œ ์š”์ฒญ์„ ๋ณด๋‚ด ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ๋กœ์ง์€ ๊ฒŒ์‹œ๊ธ€ ์—…๋กœ๋“œ ์‹œ ํ•œ ๋ฒˆ์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ ์ƒ๊ธธ Storage์™€์˜ ๋„คํŠธ์›Œํฌ ํ†ต์‹  ์ง€์—ฐ์„ ์ค„์ด๊ณ , ์ด๋ฏธ์ง€ ํŒŒ์ผ ๊ด€๋ฆฌ ๋กœ์ง์„ ์ปดํฌ๋„ŒํŠธ ๋‹จ์œ„๋กœ ์ง‘์ค‘ํ™”ํ•˜๋ ค๋Š” ์˜๋„์—์„œ ๋น„๋กฏ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋˜, house ํ…Œ์ด๋ธ”์—๋Š” ์ด๋ฏธ์ง€ ์ด๋ฆ„๋งŒ ์ €์žฅํ•˜๊ณ  ๋‹ค๋ฅธ ํŽ˜์ด์ง€์—์„œ ํ•ด๋‹น ์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ๊ฐ€์ง€๊ณ  ๋ถˆ๋Ÿฌ์™€ ๋ณด์—ฌ์ฃผ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด, ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€๋ฅผ temporary ํด๋”์— ๋ณด๊ด€ํ•œ ๋’ค ๊ฒŒ์‹œ๊ธ€์ด ์ œ์ถœ๋˜๋ฉด ์ตœ์ข…์ ์œผ๋กœ storage/images/houseId ํด๋”๋กœ ์ด๋™์‹œํ‚ค๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ด€๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”—

์ดˆ๊ธฐ ๊ตฌํ˜„๊ณผ ๊ด€๋ จํ•ด์„œ๋Š” Multi Image Form(๋‹ค์ค‘ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ) & supabase storage ๊ฒŒ์‹œ๊ธ€์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.

๊ทธ๋Ÿฌ๋‚˜, ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์€ ๋„คํŠธ์›Œํฌ ์˜์กด์„ฑ์ด ์ง€๋‚˜์น˜๊ฒŒ ๋†’์•„์ง€๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค:

  1. ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๊ณ  URL์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ณผ์ •์—์„œ, ์ด๋ฏธ์ง€ ํŒŒ์ผ์˜ ํฌ๊ธฐ๊ฐ€ ํฐ ๊ฒฝ์šฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋กœ๋”ฉ ์†๋„๊ฐ€ ๋งค์šฐ ์ง€์—ฐ๋˜์–ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ•ด์น˜๊ณ  ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.
  2. ์ด๋ฏธ์ง€๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ๊ฒฝ์šฐ, ๋ถˆํ•„์š”ํ•œ ์—…๋กœ๋“œ ๋ฐ ์‚ญ์ œ ์š”์ฒญ์ด ๋ฐ˜๋ณต๋˜๋ฉฐ ๋„คํŠธ์›Œํฌ ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ณ  ์„ฑ๋Šฅ์ด ์ €ํ•˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค.
  3. ํŽ˜์ด์ง€๋ฅผ ๋ฒ—์–ด๋‚˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ, temporary ํด๋”์— ์ด๋ฏธ ์—…๋กœ๋“œ๋œ ์ด๋ฏธ์ง€๊ฐ€ ๋‚จ์•„ ๋ฐ์ดํ„ฐ๋ฅผ ์ •๋ฆฌํ•˜๋Š” ์ถ”๊ฐ€์ ์ธ ํ•„ํ„ฐ๋ง ๋กœ์ง์ด ํ•„์š”ํ–ˆ์Šต๋‹ˆ๋‹ค.
  4. ์ด๋ฏธ์ง€๋ฅผ temporary ํด๋”์—์„œ houseId ํด๋”๋กœ ์ด๋™ํ•œ ๋’ค temporary๋ฅผ ๋น„์šฐ๋Š” ๊ณผ์ •์€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋กœ์ง์„ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค๊ณ  ์œ ์ง€๋ณด์ˆ˜๋ฅผ ์–ด๋ ต๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค.

โ˜˜๏ธ Refactor

๐ŸŽฏ Refactoring Goal

  • ์ด๋ฏธ์ง€๋ฅผ Storage์— ์ฆ‰์‹œ ์—…๋กœ๋“œํ•˜์ง€ ์•Š๊ณ  ๋กœ์ปฌ์—์„œ ๊ด€๋ฆฌํ•ด ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ œ๊ฑฐํ•˜๊ณ , ๊ฒŒ์‹œ๊ธ€ ์ œ์ถœ ์‹œ ์ตœ์ข…์ ์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.

  • ์ด๋ฏธ์ง€๋ฅผ Storage์™€ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์†๋„๋ฅผ ๊ฐœ์„ ํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

  • ์ค‘๋ณต๋œ ์ด๋ฏธ์ง€ ์ด๋™/์‚ญ์ œ ๋กœ์ง์„ ์ถ”์ƒํ™”ํ•˜๊ณ , ํ•จ์ˆ˜์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ž…๋‹ˆ๋‹ค.

โš ๏ธ

๋ฆฌํŒฉํ† ๋ง ๊ณผ์ •์—์„œ ๊ฐ ๊ธฐ๋Šฅ์„ ๋ช…ํ™•ํžˆ ๋“œ๋Ÿฌ๋‚ด๋„๋ก ํ•จ์ˆ˜๋ช… ๋ฐ ๋ณ€์ˆ˜๋ช…์„ ์ˆ˜์ •ํ•˜์—ฌ, ๊ธฐ์กด ์ด๋ฆ„๊ณผ ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป Refactoring...

1๏ธโƒฃ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ธฐ๋Šฅ

๊ธฐ์กด์—๋Š” โ‘ ย ์ด๋ฏธ์ง€๋ฅผ Storage์— ์—…๋กœ๋“œํ•œ ๋’ค, โ‘กย ํ•ด๋‹น ํŒŒ์ผ์˜ Storage URL์„ ๊ฐ€์ ธ์™€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๊ฐ„๋‹จํ•ด ๋ณด์ด์ง€๋งŒ, API ์š”์ฒญ์ด ๋‘ ๋ฒˆ ๋ฐœ์ƒํ•˜๋ฉด์„œ ๋ Œ๋”๋ง ์†๋„๊ฐ€ ๋А๋ ค์ง€๊ณ , ์‚ฌ์šฉ์ž ๊ฒฝํ—˜(UX)์ด ์ €ํ•˜๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ œ๊ณตํ•˜๋Š” URL.createObjectURL๏ผŠ API๋ฅผ ํ™œ์šฉํ•ด ์ด๋ฏธ์ง€๋ฅผ ๋กœ์ปฌ์—์„œ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“

URL.createObjectURL

  • ํŒŒ์ผ ๋˜๋Š” Blob ๊ฐ์ฒด๋ฅผ ์ž…๋ ฅ๋ฐ›์•„ ๋กœ์ปฌ URL์„ ์ƒ์„ฑํ•˜๋ฉฐ, ์ด URL์€ ํŒŒ์ผ์„ ์ž„์‹œ์ ์œผ๋กœ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Storage์— ์—…๋กœ๋“œํ•˜์ง€ ์•Š๊ณ ๋„, ์‚ฌ์šฉ์ž๊ฐ€ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€๋ฅผ ์ฆ‰์‹œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { files } = e.target;
    if (!files) return;

	// ์ž…๋ ฅ๋ฐ›์€ ๋‹ค์ค‘ ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ฐฐ์—ด์„ ๋Œ๋ฉด์„œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ์— ์‚ฌ์šฉํ•  url์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    const fileArray = Array.from(files).map((file) => {
	// ๊ณ ์œ  ๋กœ์ปฌ URL์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
    const blobUrl = URL.createObjectURL(file);
      // ์ด ๋–„, ์ƒ์„ฑ๋˜๋Š” url์€ ๊ณ ์œ ํ•œ ์ด๋ฆ„์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด๋ฏธ์ง€์˜ ๊ณ ์œ  ์ด๋ฆ„์œผ๋กœ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    const imageUuid = blobUrl.split('/').pop() as string;
      return {
        file: new File([file], imageUuid, { type: file.type }),
        preview: blobUrl,
        name: imageUuid,
      };
    });

	// ์ด๋ฏธ์ง€์˜ file, url, name์„ ๊ฐ๊ฐ ์ƒํƒœ์— ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.
    setImageFiles((prev) => [...prev, ...fileArray.map((item) => item.file)]);
    setPreviewUrls((prev) => [...prev, ...fileArray.map((item) => item.preview)]);
    form.setValue('house_img', [...form.getValues('house_img'), ...fileArray.map((item) => item.name)]);
  };
  • Storage ์—…๋กœ๋“œ ๋ฐ URL ๋ฐ˜ํ™˜ ๊ณผ์ •์„ ์ƒ๋žตํ•˜๊ณ , ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๋ฐ”๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ฒ˜๋ฆฌํ•ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ฆ‰์‹œ ๋ Œ๋”๋งํ•ด UX๋ฅผ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ตœ์ข… ์ œ์ถœ์‹œ์—๋งŒ Storage์— ์—…๋กœ๋“œํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ตœ์†Œํ™”ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • createObjectURL์—์„œ ์ƒ์„ฑ๋œ URL์˜ ์ผ๋ถ€๋ฅผ ์‚ฌ์šฉํ•ด ์ด๋ฏธ์ง€ ์ด๋ฆ„ ์ค‘๋ณต ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ‘€

previewUrl ์ƒํƒœ ์ œ๊ฑฐ ๊ณ ๋ ค

  • file์„ ๋ฐ›์•„์„œ ์ •์ œํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์น˜๋ฏ€๋กœ ImageFiles๋ฅผ file๋ฐฐ์—ด์ด ์•„๋‹Œ file๊ณผ url์„ ๊ฐ€์ง€๋Š” ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•˜์—ฌ ํ•˜๋‚˜์˜ ์ƒํƒœ๋กœ ๊ด€๋ฆฌํ•˜๊ณ ์ž ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, previewUrl์€ MultiImageForm ์ปดํฌ๋„ŒํŠธ ๋‚ด์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋Š” ์ƒํƒœ์ด๊ณ , ImageFiles๋ฅผ ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋กœ๋ถ€ํ„ฐ props๋กœ ์ „๋‹ฌ๋ฐ›์œผ๋ฏ€๋กœ ๋ถˆํ•„์š”ํ•œ ์˜์กด์„ฑ์„ ์ถ”๊ฐ€ํ•œ๋‹ค๊ณ  ์ƒ๊ฐ๋˜์–ด์„œ ๊ธฐ์กด์˜ previewUrl๋กœ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์„ ๊ณ ์ˆ˜ํ•˜๊ธฐ๋กœ ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

2๏ธโƒฃ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ๋ฐ ์ˆ˜์ • ํ›„ ์ด๋ฏธ์ง€ ์ด๋™

๊ธฐ์กด ์ฝ”๋“œ๋Š” ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ๋ฐ ์ˆ˜์ • ํ•จ์ˆ˜ ๋‚ด์—์„œ ์ด๋ฏธ์ง€ ์ด๋™ ๋ฐ ์‚ญ์ œ ๋กœ์ง์ด ์ค‘๋ณต๋˜์–ด ์žˆ์—ˆ๊ณ , ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ๋–จ์–ด์ง‘๋‹ˆ๋‹ค.

๐Ÿ‘€

Trigger ๊ณ ๋ ค

  • ๊ฒŒ์‹œ๊ธ€ ๋“ฑ๋ก ํ›„ ์ƒ์„ฑ๋˜๋Š” ๊ฒŒ์‹œ๊ธ€id๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•ด, Trigger๋ฅผ ํ†ตํ•ด ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ์‹œ ์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ด๋™์‹œํ‚ค๋Š” ๋ฐฉ์‹์„ ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ํ•˜์ง€๋งŒ, Supabase์—์„œ๋Š” SQL ํ•จ์ˆ˜๋กœ storage ๋‚ด๋ถ€์˜ ํŒŒ์ผ์„ ์ด๋™ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ  ์žˆ์—ˆ๊ณ , ํŒ€์›๋“ค๊ณผ ํ˜‘์˜ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง์ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ ˆ๋ฒจ๋กœ ๋ถ„๋ฆฌ๋˜๋ฉด ๋””๋ฒ„๊น… ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ๋ฉด์—์„œ ๊ณผ๋„ํ•œ ์ถ”์ƒํ™”๋ผ๋Š” ๊ฒฐ๋ก ์— ๋‹ค๋‹ค๋ž์Šต๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ, ํ”„๋ก ํŠธ์•ค๋“œ ์ฝ”๋“œ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค.

๊ด€๋ จ ์ž‘์—…์„ ํ•จ์ˆ˜๋กœ ์ถ”์ƒํ™”ํ•ด ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ด๊ณ  ๋กœ์ง์„ ํ†ตํ•ฉํ•˜์—ฌ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ/์ˆ˜์ • ๋กœ์ง์—์„œ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

Storage์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ , ์ด๋™ํ•˜๊ณ  ์‚ญ์ œํ•˜๋Š” ๊ฐ Storage API์š”์ฒญ์„ ์ž‘์„ฑํ•˜์—ฌ ๋กœ์ง ์ž‘์„ฑ ์‹œ ์ค‘๋ณตํ•˜์—ฌ ์ž‘์„ฑํ•˜์ง€ ์•Š๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

export const getImageList = async (path: string) => {
  const { data, error } = await supabase.storage.from('images').list(path);
  if (error)
    throw new Error(
      `์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฝ๋กœ: ${path}, ์—๋Ÿฌ: ${error.message}`,
    );
  return data;
};

export const moveImage = async (source: string, destination: string) => {
  const { error } = await supabase.storage
    .from('images')
    .move(source, destination);
  if (error)
    throw new Error(
      `์ด๋ฏธ์ง€ ์ด๋™ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์†Œ์Šค: ${source}, ๋ชฉ์ ์ง€: ${destination}, ์—๋Ÿฌ: ${error.message}`,
    );
};

export const removeImage = async (path: string) => {
  const { error } = await supabase.storage.from('images').remove([path]);
  if (error)
    throw new Error(
      `์ด๋ฏธ์ง€ ์‚ญ์ œ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ฒฝ๋กœ: ${path}, ์—๋Ÿฌ: ${error.message}`,
    );
};

์ž‘์„ฑํ•œ API ์š”์ฒญ ํ•จ์ˆ˜๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์™€ ์ด๋™ํ•˜๊ณ , ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์™€ ์‚ญ์ œํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ/์ˆ˜์ • ๋กœ์ง์—์„œ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

// ์‚ฌ์šฉ์ž๊ฐ€ ์ƒˆ๋กœ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ๊ฒŒ์‹œ๊ธ€ID ํด๋”๋กœ ์ด๋™ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
const moveImagesToStorage = async (userId: string, houseId: string) => {
  const tempPath = `house/${userId}/temp`;
  const movePath = `house/${userId}/${houseId}`;

  const images = await getImageList(tempPath);
  const movePromises = images.map(async imgObj => {
    moveImage(`${tempPath}/${imgObj.name}`, `${movePath}/${imgObj.name}`);
  });

  // Promise.all์„ ์‚ฌ์šฉํ•ด ๊ฐ ์š”์ฒญ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  await Promise.all(movePromises);
};

// ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ •์‹œ ์‚ฌ์šฉ์ž๊ฐ€ ๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•œ ๊ฒฝ์šฐ, ํ•ด๋‹น ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
const deleteUnusedImagesFromStorage = async (
  userId: string,
  houseId: string,
  images: string[],
) => {
  const storageImages = await getImageList(`house/${userId}/${houseId}`);

  const removedImages = storageImages.filter(img => !images.includes(img.name));
  const removePromises = removedImages.map(async img => {
    removeImage(`house/${userId}/${houseId}/${img}`);
  });

  // Promise.all์„ ์‚ฌ์šฉํ•ด ๊ฐ ์š”์ฒญ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  await Promise.all(removePromises);
};

export const useRegistHouse = () => {
  const { mutate: registHouse, isPending: isRegisteringHouse } = useMutation({
    mutationFn: ...,
    onSuccess: async (houseId, variables) => {
	    // ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ์‹œ, ์ด๋ฏธ์ง€๋ฅผ ์ด๋™ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
      await moveImagesToStorage(variables.user_id, houseId);
    },
  });
  return { registHouse, isRegisteringHouse };
};

export const useUpdateHouse = () => {
  const { mutate: updateHouse, isPending: isUpdatingHouse } = useMutation({
    mutationFn: ...,
    onSuccess: async (_, { houseId, houseData }) => {
	    // ๊ฒŒ์‹œ๊ธ€ ์ˆ˜์ • ์‹œ, ์‚ญ์ œ๋œ ์ด๋ฏธ์ง€๋ฅผ ํ•„ํ„ฐ๋งํ•˜๊ณ  ์ƒˆ๋กœ ์˜ฌ๋ฆฐ ์ด๋ฏธ์ง€๋ฅผ ์ด๋™ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
      const totalImage = [houseData.representative_img, ...houseData.house_img];
      await deleteUnusedImagesFromStorage(
        houseData.user_id,
        houseId,
        totalImage,
      );
      await moveImagesToStorage(houseData.user_id, houseId);
    },
  });
  return { updateHouse, isUpdatingHouse };
};
  • ์ด๋ฏธ์ง€๋ฅผ ์ด๋™ ๋ฐ ์‚ญ์ œํ•˜๋Š” API๋“ค์„ ๋…๋ฆฝ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๋„๋ก ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค. Supabase Storage์™€ ์ƒํ˜ธ์ž‘์šฉ ํ•˜๋Š” ํ•จ์ˆ˜์—์„œ ์ด๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ฝ”๋“œ์˜ ์˜๋„๋ฅผ ๋ช…ํ™•ํžˆ ํ‘œํ˜„ํ•˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ์ด ๋•Œ, ์ด๋ฏธ์ง€ ์ด๋™ ๋ฐ ์‚ญ์ œ์‹œ Promise.all๏ผŠ์„ ์‚ฌ์šฉํ•ด ๋น„๋™๊ธฐ ์ž‘์—…์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•ด ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

  • ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ ๋ฐ ์ˆ˜์ •์‹œ ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋„๋ก ํ•ด ๋กœ์ง์„ ๋‹จ์ˆœํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ”—

Promise.all

Promise.all์— ๋Œ€ํ•ด์„œ๋Š” ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ Promise, Async/Await ๊ฒŒ์‹œ๊ธ€์„ ์ฐธ๊ณ ํ•˜์„ธ์š”.


๐ŸŒˆ ๊ฐœ์„  ๋ฐ ์„ฑ๊ณผ

์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 

  • ๋กœ์ปฌ์—์„œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ๊ด€๋ฆฌํ•˜์—ฌ ๋„คํŠธ์›Œํฌ ์ƒํƒœ์— ์ƒ๊ด€์—†์ด ์‚ฌ์šฉ์ž๊ฐ€ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ์ฆ‰๊ฐ์ ์œผ๋กœ ํ‘œ์‹œ๋˜๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๊ธฐ์กด์—๋Š” Storage ์—…๋กœ๋“œ์™€ URL ๋ฐ˜ํ™˜ ๊ณผ์ •์—์„œ ๋ Œ๋”๋ง์ด ์ง€์—ฐ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ์ด๋ฅผ ์™„์ „ํžˆ ์ œ๊ฑฐํ•˜์—ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

๋„คํŠธ์›Œํฌ ์š”์ฒญ ๊ฐ์†Œ ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™”

  • ์ด๋ฏธ์ง€ ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•œ ๋’ค URL์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ์กด ๋กœ์ง ๋Œ€์‹ , ์ตœ์ข… ์ œ์ถœ ์‹œ ํ•œ ๋ฒˆ์— Storage๋กœ ์—…๋กœ๋“œ ํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ์‹œ ๊ธฐ์ค€์œผ๋กœ Storage์— ์š”์ฒญํ•˜๋Š” ๋„คํŠธ์›Œํฌ ์ค„์—ฌ, ๊ธฐ์กด ๋Œ€๋น„ ์š”์ฒญ ํšŸ์ˆ˜ 50% ๊ฐ์†Œ์™€ ์‘๋‹ต ์‹œ๊ฐ„ ํ‰๊ท  30% ๊ฐ์†Œ๋ผ๋Š” ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ๊ณผ๋ฅผ ํ™•์ธํ–ˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ

  • moveImage, removeImage, getImageList ๋“ฑ Storage์™€ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ๋ชจ๋“ˆํ™”ํ•˜์—ฌ ํ•จ์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด, ๋™์ผ ๋กœ์ง์„ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์–ด ์ค‘๋ณต ์ฝ”๋“œ๋ฅผ ์ œ๊ฑฐํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ฑ๊ณผ ์ˆ˜์ • ๋กœ์ง์—์„œ ์ค‘๋ณต๋œ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ํ†ตํ•ฉํ•ด, ์ฝ”๋“œ์˜ ๋ช…ํ™•์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.

๊ตฌ์กฐ์  ๋‹จ์ˆœํ™”

  • ๊ธฐ์กด์˜ ๋ณต์žกํ•œ ํด๋” ์ด๋™ ๋ฐ ์ •๋ฆฌ ๋กœ์ง์„ ๋‹จ์ˆœํ™”ํ•˜์—ฌ, ์ฝ”๋“œ๋ฅผ ๋” ์ง๊ด€์ ์œผ๋กœ ๊ตฌ์„ฑํ–ˆ๊ณ , Storage์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์ตœ์†Œํ™”ํ•ด ๊ตฌ์กฐ์  ๋ช…ํ™•์„ฑ์„ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค.

  • Promise.all์„ ํ™œ์šฉํ•ด ๋น„๋™๊ธฐ ์ž‘์—…์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•ด ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ–ˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป ๋ฐฐ์šด ์ 

์ด๋ฒˆ ๋ฆฌํŒฉํ† ๋ง์„ ํ†ตํ•ด ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ตœ์†Œํ™”ํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ์™€ ๋น„๋™๊ธฐ ์ž‘์—… ์ตœ์ ํ™”๊ฐ€ ์–ด๋–ป๊ฒŒ ์ค‘์š”ํ•œ ์ง€ ์ฒด๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๋„คํŠธ์›Œํฌ ์˜์กด์„ฑ์ด ํด ๊ฒฝ์šฐ ๋„คํŠธ์›Œํฌ ์ƒํƒœ์— ๋”ฐ๋ผ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ํฌ๊ฒŒ ์ขŒ์šฐ๋˜๋ฏ€๋กœ, ์ธํ„ฐ๋„ท์ด ๋А๋ฆฌ๊ฑฐ๋‚˜ ๋ถˆ์•ˆ์ •ํ•œ ํ™˜๊ฒฝ๋„ ๊ณ ๋ คํ•ด์„œ ๊ฐœ๋ฐœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿ‘‰๐Ÿป ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋งค๋ฒˆ ์š”์ฒญํ•˜์ง€ ์•Š๊ณ , ๋กœ์ปฌ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•ด์„œ ๋„คํŠธ์›Œํฌ ์˜์กด์„ฑ์„ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์„œ๋ฒ„๋กœ์˜ ์š”์ฒญ ํšŸ์ˆ˜๋Š” ๊ณง ์„œ๋ฒ„ ๋น„์šฉ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„๋กœ ํ•œ ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ์„ค๊ณ„ ๋ฐฉ์‹์œผ๋กœ ์„œ๋ฒ„ ๋น„์šฉ๋„ ์ ˆ๊ฐํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

  • ๋ณต์žกํ–ˆ๋˜ ํด๋” ์ด๋™ ๋ฐ ์ •๋ฆฌ ๋กœ์ง์„ ๋‹จ์ˆœํ™”ํ•˜๊ณ , ๋ถˆํ•„์š”ํ•œ ์ž‘์—…์„ ์ œ๊ฑฐํ•ด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์ด๊ณ  ์œ ์ง€ ๋ณด์ˆ˜์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค. ํŒ€์›๊ณผ ํ˜‘์—…์‹œ ์ฝ”๋“œ์— ๋Œ€ํ•ด ํšŒ์˜ํ•˜๊ธฐ ์œ„ํ•ด ์ฝ๊ธฐ ์‰ฝ๊ณ  ์ดํ•ดํ•˜๊ธฐ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•จ์„ ๋‹ค์‹œ ํ•œ ๋ฒˆ ์ฒด๊ฐ ํ–ˆ์Šต๋‹ˆ๋‹ค.

profile
๋ฐฑ ๋ฒˆ์„ ๋ณด๋ฉด ํ•œ ๊ฐ€์ง€๋Š” ์•ˆ๋‹ค ๐Ÿ‘€

0๊ฐœ์˜ ๋Œ“๊ธ€