image

๐Ÿ“Œ Table Of Contents



1. ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

ํ–‰๋™๋Œ€์žฅ(ํ–‰๋ณตํ•œ ๋™๋„ค๋ฅผ ์œ„ํ•œ ๋Œ€ํ™”์˜ ์žฅ์†Œ)

๋™๋„ค ์‚ฌ๊ฑด,์‚ฌ๊ณ ์™€ ๋ฌธ์ œ์— ๋ณด๋‹ค ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•˜๊ณ  ๊ณต์œ ํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ ์„œ๋น„์Šค


2. ๊ฐœ๋ฐœ ํ™˜๊ฒฝ

react react-query KakaoMap


3. ํŒ€ ๋ฉค๋ฒ„ ๋ฐ ์—ญํ• 

๐Ÿ’™ ์ด์†Œํ˜„ ๐Ÿ’™

์—ญํ• :

  • ํ—ค๋”
    • ํŽ˜์ด์ง€ ์ด๋™ ๋ฐ ๋กœ๊ทธ์•„์›ƒ
  • ๋ฉ”์ธํŽ˜์ด์ง€
    • ๋ฐ˜์‘ํ˜• UI ๊ตฌํ˜„
    • Kakao Map API๋กœ ์ง€๋„์™€ ๋งˆ์ปค ๋ฐ ํด๋Ÿฌ์Šคํ„ฐ๋Ÿฌ ์‚ฌ์šฉ
    • ํ˜„ ์ง€์—ญ ๊ฒŒ์‹œ๋ฌผ ์ธํ”ผ๋‹ˆํŠธ ์Šคํฌ๋กค ๊ตฌํ˜„
    • ์ง€์—ญ ๊ฒ€์ƒ‰ ๋ฐ ์ด๋™ ๊ธฐ๋Šฅ
    • ํ˜„์žฌ ์œ„์น˜ ์ด๋™ ๊ธฐ๋Šฅ
    • ์ง€๋„ ํ˜„ ์ง€์—ญ ์ฃผ์†Œ ํ‘œ์‹œ
  • ๋งˆ์ดํŽ˜์ด์ง€
    • ๋ฐ˜์‘ํ˜• UI ๊ตฌํ˜„
    • ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒŒ์‹œํ•œ ๊ฒŒ์‹œ๋ฌผ, ๋Œ“๊ธ€์„ ์“ด ๊ฒŒ์‹œ๋ฌผ, '๋‚˜๋„ ๋ถˆํŽธํ•ด์š”' ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ๋ณด๊ธฐ
    • ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ฒ˜๋ฆฌ

๐Ÿ’™ ์ž„ํ•˜๋ฃจ ๐Ÿ’™

์—ญํ• :

  • ํด๋” ๊ตฌ์กฐ ์ •๋ฆฝ ๋ฐ eslint, prettier ์„ค์ •
  • ํšŒ์›๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ ๊ตฌํ˜„ ๋ฐ ๋ฐ˜์‘ํ˜• UI
  • ์ผ๋ฐ˜ ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… ๋ฐ ์†Œ์…œ ๋กœ๊ทธ์ธ (์นด์นด์˜คํ†ก) ๊ตฌํ˜„
  • JWT(Access Token, Refresh Token) ๊ด€๋ฆฌ
  • ์ƒ์„ธ ํŽ˜์ด์ง€
    • ๊ฒŒ์‹œ๊ธ€ ์กฐํšŒ, '๋‚˜๋„ ๋ถˆํŽธํ•ด์š”', 'ํ•ด๊ฒฐํ–ˆ์–ด์š”' ๊ธฐ๋Šฅ, ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ ๊ธฐ๋Šฅ, ๋ฐ˜์‘ํ˜• UI
    • ๋Œ“๊ธ€ ๋ฐ˜์‘ํ˜• UI ์ž‘์—…
  • 'ํ•ด๊ฒฐํ–ˆ์–ด์š”' ๋ˆ„์  ์‹œ ์™„๋ฃŒ ๊ฒŒ์‹œ๊ธ€ ๊ตฌํ˜„
  • ๋งˆ์ดํŽ˜์ด์ง€
    • ์œ ์ € ์ •๋ณด ์กฐํšŒ, ์ด๋ฉ”์ผ ์ถ”๊ฐ€ ๋“ฑ๋ก, ๋‹‰๋„ค์ž„ ๋ณ€๊ฒฝ, ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ณ€๊ฒฝ, ํšŒ์› ํƒˆํ‡ด ๊ธฐ๋Šฅ, ๋ฐ˜์‘ํ˜• UI
  • 404 ํŽ˜์ด์ง€ ๊ตฌํ˜„
  • ๋ฒ”์šฉ์ ์œผ๋กœ ์‚ฌ์šฉ๋˜๋Š” api instance ๋ฐ interceptor, ๋ชจ๋‹ฌ, ๋ฒ„ํŠผ, Media Query, Global Theme, Toast ์•Œ๋žŒ, confetti HOC ๋“ฑ ๊ตฌํ˜„

๐Ÿ’™ ์ด์ค€ํ˜ธ ๐Ÿ’™

์—ญํ• :

  • AWS ํ”„๋ก ํŠธ ๋ฐฐํฌ(https), CI / CD ๊ตฌ์ถ•

  • ๊ฒŒ์‹œ๊ธ€ CRUD ๊ธฐ๋Šฅ ๊ตฌํ˜„

  • ๋Œ“๊ธ€ ๊ธฐ๋Šฅ: ์กฐํšŒ, ์ž‘์„ฑ, ์‚ญ์ œ

  • Kakao ์ง€๋„ API๋ฅผ ํ™œ์šฉํ•œ ์œ„๋„, ๊ฒฝ๋„, ์ฃผ์†Œ ์ „์†ก ๋ฐ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ ๊ธฐ๋Šฅ

  • FormData ์ด๋ฏธ์ง€ ์ „์†ก ์š”์ฒญ ๊ธฐ๋Šฅ

  • SSE ์—ฐ๊ฒฐ์„ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๊ธฐ๋Šฅ

  • ๋žœ๋”ฉํŽ˜์ด์ง€, ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ, ์ˆ˜์ • ํŽ˜์ด์ง€, 404ํŽ˜์ด์ง€์— ๋Œ€ํ•œ PC, Tablet, Mobile ๋ฐ˜์‘ํ˜• UI ๊ตฌํ˜„


4. ์ฃผ์š” ๊ธฐ๋Šฅ

๐Ÿ“Œ ๋ถˆํŽธ์‚ฌํ•ญ ๊ณต์œ 

์ž์‹ ์ด ๋Š๋‚€ ๋™๋„ค์˜ ๋ถˆํŽธ์‚ฌํ•ญ์— ๋Œ€ํ•ด ๊ฒŒ์‹œ๊ธ€๊ณผ ๋Œ“๊ธ€์„ ์ž‘์„ฑํ•˜๋ฉฐ, ์ด๋ฅผ ํ†ตํ•ด ์ปค๋ฎค๋‹ˆํ‹ฐ ๋‚ด์—์„œ ์ž์œ ๋กญ๊ฒŒ ์˜๊ฒฌ์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ ์ฃผ์†Œ ๊ฒ€์ƒ‰ ํ›„ ์ง€๋„ ์ด๋™

ํŠน์ • ์ง€์—ญ์˜ ์ •๋ณด์— ๊ด€์‹ฌ์ด ์žˆ๋‹ค๋ฉด "์ง€์—ญ ๊ฒ€์ƒ‰" ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ํ•ด๋‹น ์ง€์—ญ์œผ๋กœ ์‰ฝ๊ฒŒ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์‹ ์ด ์‚ด๊ณ  ์žˆ๋Š” ์ง€์—ญ ์™ธ์˜ ๋‹ค๋ฅธ ์ง€์—ญ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ถˆํŽธ์‚ฌํ•ญ๋“ค๋„ ํ™•์ธํ•˜๊ณ  ๊ณต๊ฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ ์ง€๋„๋กœ ๋ณด๋Š” ๋™๋„ค์˜ ๋ถˆํŽธ์‚ฌํ•ญ

์ง€๋„ ์ƒ์—์„œ ์ง๊ด€์ ์œผ๋กœ ์ฃผ๋ณ€์˜ ๋ถˆํŽธ์‚ฌํ•ญ๋“ค์„ ํ•œ๋ˆˆ์— ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์œ„์น˜ ๊ทผ๋ฐฉ์—์„œ ์–ด๋–ค ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ๋Š”์ง€ ์‰ฝ๊ฒŒ ์•Œ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ ๋งˆ์ดํŽ˜์ด์ง€

๋งˆ์ด ํŽ˜์ด์ง€์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๊ธ€, '๋‚˜๋„ ๋ถˆํŽธํ•ด์š”'๋กœ ์‘์›ํ•œ ๊ฒŒ์‹œ๊ธ€, ๊ทธ๋ฆฌ๊ณ  ์ž‘์„ฑํ•œ ๋Œ“๊ธ€์— ๋Œ€ํ•œ ๊ฒŒ์‹œ๊ธ€ ๋“ฑ์„ ํ•œ๋ฒˆ์— ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”๊ฐ€๋กœ, ํšŒ์›์ •๋ณด ์ˆ˜์ •, ํšŒ์›ํƒˆํ‡ด ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ๊ฐœ์ธ ์ •๋ณด ๊ด€๋ฆฌ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ โ€œ๋‚˜๋„ ๋ถˆํŽธํ•ด์š”โ€, โ€œํ•ด๊ฒฐ๋์–ด์š”โ€ ๊ธฐ๋Šฅ

์ผ์ƒ์—์„œ ๊ฒช๋Š” ๋ถˆํŽธํ•จ์„ ๊ณต๊ฐํ•˜๊ณ , ์ด๋ฅผ ์ปค๋ฎค๋‹ˆํ‹ฐ์— ๊ณต์œ ํ•˜์—ฌ ์ธ์ง€๋„๋ฅผ ๋†’์ด๋Š” "์‚ฌ์ด๋ Œ" ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.  ๋ฐ˜๋ฉด, 'ํ•ด๊ฒฐ๋์–ด์š”' ๋ฒ„ํŠผ์€ ํŠน์ • ๋ฌธ์ œ์ ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ ์ƒํ™ฉ์„ ์‚ฌ์šฉ์ž๋“ค๊ณผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋“ค์€ ๊ฒŒ์‹œ๊ธ€์˜ ์ตœ์‹  ์ƒํƒœ๋ฅผ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•˜๊ณ , ๋ฌธ์ œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ํ•ด๊ฒฐ๋˜์—ˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ผ์ • ๊ฐœ์ˆ˜ ์ด์ƒ์˜ โ€˜ํ•ด๊ฒฐ๋์–ด์š”โ€™ ๊ฐ€ ๋ˆ„์ ๋˜๋ฉด, ๊ฒŒ์‹œ๊ธ€์ด ํ•ด๊ฒฐ ์™„๋ฃŒ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

๐Ÿ“Œ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๊ธฐ๋Šฅ

์‚ฌ์šฉ์ž๋Š” ๋ณธ์ธ์ด ์ž‘์„ฑํ•œ ๊ฒŒ์‹œ๋ฌผ์— ํ™œ๋™(๋Œ“๊ธ€, ๋‚˜๋„ ๋ถˆํŽธํ•ด์š”, ํ•ด๊ฒฐ๋์–ด์š” ๋“ฑ)์ด ๋ฐœ์ƒํ•˜๋ฉด ๊ฐ„๋žตํ•œ ๋‚ด์šฉ๊ณผ ์‹œ๊ฐ„์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ์ฝ์€ ์•Œ๋ฆผ๊ณผ ์ฝ์ง€ ์•Š์€ ์•Œ๋ฆผ์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ๊ณ , ๊ฐ๊ฐ์˜ ์•Œ๋ฆผ์„ ํด๋ฆญํ•˜๋ฉด ํ•ด๋‹น ๊ฒŒ์‹œ๋ฌผ๋กœ ์ด๋™ํ•ด ์ž์„ธํ•œ ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฒŒ์‹œ๋ฌผ์ด โ€˜ํ•ด๊ฒฐ์™„๋ฃŒโ€™ ์ฒ˜๋ฆฌ๋œ ๊ฒฝ์šฐ์—๋Š” ์›นํŽ˜์ด์ง€ ๋‚ด์—์„œ์˜ ์•Œ๋ฆผ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์ด๋ฉ”์ผ๋„ ๋ฐ›์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Œ ๋กœ๊ทธ์ธ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ

access token, refresh token์„ ํ†ตํ•œ ์ผ๋ฐ˜ ๋กœ๊ทธ์ธ ๋ฐ ์นด์นด์˜ค ์†Œ์…œ ๋กœ๊ทธ์ธ์„ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.


5. ๊ธฐ์ˆ ์  ์˜์‚ฌ ๊ฒฐ์ •

์š”๊ตฌ์‚ฌํ•ญ ์„ ํƒ ๊ธฐ์ˆ ์„ ์„ ํƒํ•œ ์ด์œ  ๋ฐ ๊ทผ๊ฑฐ
์•ˆ์ •์ ์ธ ์ฝ”๋“œ TypeScript ํƒ€์ž… ์•ˆ์ •์„ฑ ์ œ๊ณต: TypeScript์˜ ์ •์  ํƒ€์ดํ•‘ ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฅ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Œ
์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ: ๋ช…์‹œ์ ์ธ ํƒ€์ž… ํ‘œ๊ธฐ๋กœ ์ธํ•ด ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›Œ์ง
๊ฐœ๋ฐœ ํšจ์œจ์„ฑ ์ฆ๋Œ€: ์ž๋™์™„์„ฑ, ์ธํ„ฐํŽ˜์ด์Šค ํ™•์ธ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Œ
ํ™•์žฅ์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด: ํฐ ๊ทœ๋ชจ์˜ ํ”„๋กœ์ ํŠธ์—์„œ ๊ตฌ์กฐ์ ์ด๊ณ  ๋ช…ํ™•ํ•œ ์ฝ”๋“œ๋กœ ์ธํ•ด ์œ ์ง€๋ณด์ˆ˜ ๋ฐ ํ™•์žฅ์ด ์šฉ์ดํ•จ
๊ฐ€๋ฒผ์šด ์ƒํƒœ ๊ด€๋ฆฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ Recoil ๋ณด์ผ๋Ÿฌ ํ”Œ๋ ˆ์ดํŠธ ์ฝ”๋“œ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Œ
์ง๊ด€์ ์ด๊ณ  ์‰ฌ์šด ์‚ฌ์šฉ๋ฒ•์œผ๋กœ ํ˜‘์—…์— ์œ ๋ฆฌ
redux ๋ณด๋‹ค ์ƒ๋Œ€์ ์œผ๋กœ ๊ฐ€๋ฒผ์šด recoil ์„ ํƒ
์„œ๋ฒ„ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ผ๊ด„๋˜๊ฒŒ ๊ด€๋ฆฌ ReactQuery v4 ์„œ๋ฒ„์ชฝ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ข€๋” ์‰ฝ๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ์‰ฌ์›Œ ๋ฐ์ดํ„ฐ ํŒจ์นญ, ์บ์‹ฑ, ๋™๊ธฐ์  ์„œ๋ฒ„์˜ ์ƒํƒœ์˜ ์—…๋ฐ์ดํŠธ์— ์šฉ์ด
๋ณ„๋„์˜ ์˜ต์…˜์„ ์ง€์›ํ•˜์—ฌ ๋ณต์žกํ•œ ์ฝ”๋“œ๋ฅผ reactQuery ๋กœ์ง์„ ํ†ตํ•ด ์งง์€ ์ฝ”๋“œ๋กœ ๋Œ€์ฒด
reactQuery์—์„œ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜์—ฌ ์‰ฝ๊ฒŒ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ(infinity query, InvalidateQueries)
ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๊ฐ€ ๊ธฐ์กด๋ณด๋‹ค ๋‹จ์ˆœํ•ด์ ธ ์œ ์ง€๋ณด์ˆ˜ํ•˜๊ธฐ ์‰ฝ๊ณ  ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๊ตฌ์ถ•
์ตœ์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋„์ž…ํ•ด ๋ด„์œผ๋กœ์จ ์ตœ์‹  ๊ธฐ์ˆ  ๋„์ž…์— ์ต์ˆ™ํ•ด์ง€๊ธฐ ์œ„ํ•จ
๋น ๋ฅธ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ CRA ๋ณต์žกํ•œ ํ™˜๊ฒฝ์„ค์ •์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋ฐ”๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์Œ(์›นํŒฉ, ๋ฐ”๋ฒจ ๋“ฑ์˜ ๋ณต์žกํ•œ ์„ค์ •์„ CRA๊ฐ€ ๋ฏธ๋ฆฌ ํ•ด์คŒ)
CRA๋Š” TS๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณต์žกํ•œ ์„ค์ • ์—†์ด ์ฆ‰์‹œ TS์™€ React์˜ ์กฐํ•ฉ์œผ๋กœ ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Œ
instance์™€ interceptor ๊ธฐ๋Šฅ Axios ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด์˜ ๋ชจ๋“  HTTP ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ์ผ๊ด€๋˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด, ์ฝ”๋“œ์˜ ๋ณต์žก์„ฑ์„ ์ค„์ž„
Axios interceptor๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์š”์ฒญ์ด๋‚˜ ์‘๋‹ต์„ ๊ฐ€๋กœ์ฑ„์„œ ์ถ”๊ฐ€์ ์ธ ์ž‘์—… ์ง„ํ–‰. ์ด๋ฅผ ํ†ตํ•ด ํ† ํฐ ๊ฐฑ์‹  ์ž‘์—…์„ ์‰ฝ๊ฒŒ ์ฒ˜๋ฆฌ
Axios instance๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ˜๋ณต์ ์ธ ์ฝ”๋“œ๋ฅผ ์ค„์ด๊ณ , ํ”„๋กœ์ ํŠธ ์ „๋ฐ˜์— ๊ฑธ์ณ ์ผ๊ด€๋œ HTTP ํด๋ผ์ด์–ธํŠธ ๊ตฌ์„ฑ์„ ์ œ๊ณต
์š”์ฒญ์ด๋‚˜ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ์ž๋™์œผ๋กœ JSON์œผ๋กœ ๋ณ€ํ™˜ํ•ด ์ฃผ์–ด ๋ฐ์ดํ„ฐ ํ˜•์‹์— ๋Œ€ํ•œ ์ถ”๊ฐ€์ ์ธ ์ฒ˜๋ฆฌ ์—†์ด๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌ
์›นํŽ˜์ด์ง€ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ CloudFront CDN(Content Delivery Network)๋ฅผ ํ™œ์šฉํ•ด ์›นํŽ˜์ด์ง€ ๋กœ๋”ฉ ์†๋„ ํ–ฅ์ƒ
์ •์  ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•˜์—ฌ ์›น์‚ฌ์ดํŠธ ์ตœ์ ํ™”, ์ปจํ…์ธ  ์ œ๊ณต ์†๋„ ํ–ฅ์ƒ
์ตœ์ ํ™”๋œ ๋ฆฌ๋ Œ๋”๋ง react-hook-form ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ์ค„์ด๊ณ  ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ด
์ž…๋ ฅ ์ปจํŠธ๋กค๊ณผ ํผ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ž„
๋‹ค์–‘ํ•œ UI ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€์˜ ํ†ตํ•ฉ์ด ์šฉ์ดํ•˜์—ฌ, ์œ ์—ฐํ•œ UI ๊ตฌํ˜„ ๊ฐ€๋Šฅ
ํ˜‘์—…์— ์œ ๋ฆฌํ•œ style tool styled-component ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ์ด ๊ฐ€๋Šฅ
JS์™€ CSS ์‚ฌ์ด์˜ ์ƒ์ˆ˜์™€ ํ•จ์ˆ˜๋ฅผ ์‰ฝ๊ฒŒ ๊ณต์œ 
media query๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‰ฌ์šด ๋ฐ˜์‘ํ˜• UI ๊ฐœ๋ฐœ ๊ฐ€๋Šฅ
์ด๋ฏธ์ง€ ๋ฐ ๋น„๋””์˜ค ๊ด€๋ฆฌ Cloudinary ์›๋ณธ ๋ฏธ๋””์–ด๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด๊ด€ํ•˜๊ณ  ํ•„์š”์— ๋”ฐ๋ผ ์—ฌ๋Ÿฌ ๋ฒ„์ „์˜ ๋ณ€ํ™˜๋œ ๋ฏธ๋””์–ด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ์ €์žฅ ๊ณต๊ฐ„์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์Œ
์ž๋™์œผ๋กœ ์ด๋ฏธ์ง€์™€ ๋น„๋””์˜ค๋ฅผ ์ตœ์ ํ™”ํ•˜์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋” ๋น ๋ฅด๊ฒŒ ์ œ๊ณตํ•˜๋ฉฐ ๋Œ€์—ญํญ ๋น„์šฉ์„ ์ ˆ๊ฐํ•จ
์ง€๋„ API Kakao ์ง€๋„ API ์ƒ๋Œ€์ ์œผ๋กœ ๋” ์ •ํ™•ํ•˜๊ณ  ์ƒ์„ธํ•œ ๊ตญ๋‚ด ์ง€๋„ ๋ฐ์ดํ„ฐ
๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ API ์ง€์›๊ณผ ๋ฌธ์„œํ™”๊ฐ€ ์ž˜ ๋˜์–ด์žˆ์–ด ๊ฐœ๋ฐœ ์‹œ๊ฐ„์„ ๋‹จ์ถ•ํ•  ์ˆ˜ ์žˆ์Œ
์นด์นด์˜ค ์„œ๋น„์Šค์— ์ต์ˆ™ํ•œ ๊ตญ๋‚ด ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ๋” ๋‚˜์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ œ๊ณตํ•จ
์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ๋ฐฑ react-toastify ์œ ์ €์—๊ฒŒ ์‹ค์‹œ๊ฐ„ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด ์œ ์ €์—๊ฒŒ ํ˜„์žฌ ์ƒํƒœ๋‚˜ ์ง„ํ–‰ ์ƒํ™ฉ์„ ์‰ฝ๊ฒŒ ์•Œ๋ฆผ
ํ•„์š”ํ•  ๋•Œ๋งŒ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜์—ฌ ๋ฆฌ์†Œ์Šค๋ฅผ ์ ˆ์•ฝํ•˜๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„ฑ๋Šฅ์„ ์œ ์ง€
์ž์ฒด์ ์œผ๋กœ ์•Œ๋ฆผ์˜ ์ž๋™ ์†Œ๋ฉธ, ์œ„์น˜ ์ง€์ •, ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ์ง€์›ํ•ด ๊ฐœ๋ฐœ ๊ณผ์ •์„ ๋‹จ์ˆœํ™”ํ•˜๊ณ  ์‹œ๊ฐ„์„ ์ ˆ์•ฝ

6. Troubleshooting

๐Ÿ“Œ ๋ฌธ์ œ 1: ๋ฉ”์ธํŽ˜์ด์ง€์—์„œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ ํ›„ ๋‹ค์‹œ ๋Œ์•„์™”์„ ๋•Œ ์œ„์น˜๊ฐ€ ์ดˆ๊ธฐ๋กœ ๋Œ์•„๊ฐ€๋Š” ๋ฌธ์ œ

์ž‘์„ฑ์ž: ์ด์†Œํ˜„

๋ฌธ์ œ: ๋ฉ”์ธํŽ˜์ด์ง€์—์„œ ์œ„์น˜๋ฅผ ์ด๋™ํ•œ ํ›„ ๋‹ค๋ฅธ ํŽ˜์ด์ง€์— ๊ฐ”๋‹ค๊ฐ€ ๋‹ค์‹œ ํ˜„ ์œ„์น˜๋กœ ์˜ฌ ๊ฒฝ์šฐ ์ง€๋„๊ฐ€ ์ƒˆ๋กญ๊ฒŒ ์ดˆ๊ธฐํ™”๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฉ”์ธํŽ˜์ด์ง€๋กœ ์˜ฌ ๋•Œ๋งˆ๋‹ค ํ˜„์žฌ ์œ„์น˜์—์„œ ์ง€๋„๊ฐ€ ์‹œ์ž‘ํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ์•ˆ:

  • ์ง€๋„ ์œ„์น˜์™€ ์คŒ ๋ ˆ๋ฒจ์„ ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€์— ์ €์žฅํ•˜์—ฌ ๋ฉ”์ธํŽ˜์ด์ง€์— ์˜ฌ ๋•Œ๋งˆ๋‹ค ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€์— ๊ฐ’์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜์—ฌ ์˜ต์…˜์„ ์„ธํŒ…ํ•˜์—ฌ ์ง€๋„ ์œ„์น˜์™€ ์คŒ ๋ ˆ๋ฒจ์„ ๋งž์ถฐ์ค๋‹ˆ๋‹ค.
  • ๋กœ์ปฌ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋Œ€์‹  ์„ธ์…˜ ์Šคํ† ๋ฆฌ์ง€๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ๋‚˜๊ฐ€๋ฉด ์„ค์ •์ด ์ดˆ๊ธฐํ™” ๋˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  useEffect(() => {
    if (!map) {
      const saveMapCenter = sessionStorage.getItem('mapCenter');
      const saveMapLevel = sessionStorage.getItem('mapLevel');

      saveMapCenter && setMapCenter(JSON.parse(saveMapCenter));
      saveMapLevel && setZoomLevel(JSON.parse(saveMapLevel));

      const options = {
        center: new window.kakao.maps.LatLng(mapCenter.lat, mapCenter.lng),
        level: zoomLevel,
      };
      setMap(new window.kakao.maps.Map(mapRef.current, options));
    }
  }, []);


๐Ÿ“Œ ๋ฌธ์ œ 2: ๋ฐœ์ „๋˜๋Š” ๊ฒ€์ƒ‰

์ž‘์„ฑ์ž: ์ด์†Œํ˜„

๋ฌธ์ œ: ๊ฒ€์ƒ‰์„ ๊ตฌํ˜„ํ•˜๋‹ค ๋ณด๋‹ˆ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ์ด์Šˆ๋ฅผ ๋งŒ๋‚ฌ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ์•ˆ:

  • ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์„ ๋•Œ ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜๋ฅผ ๋ณด๋‚ด๋Š” ๋Œ€์‹  ๋นˆ ๋ฐฐ์—ด๋กœ ์‘๋‹ต์„ ๋ณด๋‚ด๋„๋ก ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฒ€์ƒ‰์„ ์œ„ํ•˜์—ฌ API๋ฅผ ๋‘๋ฒˆ ํ˜ธ์ถœํ•ด์•ผํ•ด์„œ UX๋ฅผ ์ €ํ•ดํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋Š”๋ฐ, ๊ฒ€์ƒ‰ ๋ชฉ๋ก๊ณผ ์ขŒํ‘œ๋ฅผ ํ•œ ๋ฒˆ์— ๋ฐ›๋Š” ํ•˜๋‚˜์˜ API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
  • ๊ฒ€์ƒ‰์–ด ์ž…๋ ฅ ์ด๋ฒคํŠธ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜์—ฌ ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•˜๊ณ  ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•ด ๋””๋ฐ”์šด์‹ฑ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  const [inputValue, setInputValue] = useState<string>('');
  const [keyword, setKeyword] = useState<string>('');

  const { data } = useSearchListQuery(keyword);

  const debouncedSetKeyword = useCallback(
    useDebouncedCallback((value: string) => {
      setKeyword(value);
    }, 500),
    [],
  );

  useEffect(() => {
    debouncedSetKeyword(inputValue);
  }, [inputValue]);


๐Ÿ“Œ ๋ฌธ์ œ 3: ๋Œ“๊ธ€ ์ž‘์„ฑ, ์‚ญ์ œ์‹œ ์›นํŽ˜์ด์ง€์— ๋ฐ”๋กœ ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ

์ž‘์„ฑ์ž: ์ด์ค€ํ˜ธ

๋ฌธ์ œ: ๋Œ“๊ธ€์„ ์ž‘์„ฑ ๋˜๋Š” ์‚ญ์ œํ•  ๋•Œ ์›นํŽ˜์ด์ง€์— ์ฆ‰์‹œ ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ์•ˆ:

  • ํ˜„์žฌ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์„œ๋ฒ„์—์„œ ๋ณ€๊ฒฝ๋œ ๊ฐ’์„ useState๋กœ ๋‹ค์‹œ ๋‹ด์•„ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ๋งˆ๋‹ค ์›นํŽ˜์ด์ง€์— ๋ฐ˜์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๋” ๋ฐœ์ „๋œ ๋ฐฉ๋ฒ•์œผ๋กœ react-query์˜ useQueryClient์™€ invalidateQueries๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•˜์—ฌ ์ตœ์‹  ๊ฐ’์œผ๋กœ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค.
const queryClient = useQueryClient();
 
const deleteCommentMutation = useMutation<void, unknown, string>(
  deleteComment,
  {
    onSuccess: () => {
      queryClient?.invalidateQueries(['postDetail', postId]);
    },
  },
);

const createCommentMutation = useMutation<void, unknown, CreateCommentArgs>(
  createComment,
  {
    onSuccess: () => {
      queryClient?.invalidateQueries(['postDetail', postId]);
    },
});


๐Ÿ“Œ ๋ฌธ์ œ 4: ์—ฌ๋Ÿฌ ์žฅ์˜ ์ด๋ฏธ์ง€์™€ ๋‹ค๋ฅธ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์ „์†ก

์ž‘์„ฑ์ž: ์ด์ค€ํ˜ธ

๋ฌธ์ œ: ์—ฌ๋Ÿฌ ์žฅ์˜ ์ด๋ฏธ์ง€์™€ ๋‹ค๋ฅธ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•จ๊ป˜ ์ „์†กํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ๋ฐฉ์•ˆ:

  • ์ด๋ฏธ์ง€ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ํŒŒ์ผ ์ž์ฒด๋ฅผ ๋ฐฐ์—ด๋กœ FormData์— appendํ•˜๊ณ  ์„œ๋ฒ„์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฏธ์ง€์™€ ํ•จ๊ป˜ ๋‹ค๋ฅธ ํ˜•์‹(string, number)์˜ ๋ฐ์ดํ„ฐ๋ฅผ JSON.stringify๋กœ ์ „์†กํ•˜๊ณ , ๊ตฌ๋ณ„ํ•˜๊ธฐ ์‰ฝ๋„๋ก blob ํ˜•ํƒœ๋กœ JSON ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์‹ธ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
const sendPostRequest = async () => {
    const formData = new FormData();
    const postJSON = JSON.stringify({
      title: post.title,
      content: post.content,
      latitude: post.latitude,
      longitude: post.longitude,
      address: post.address,
    });

    const blob = new Blob([postJSON], { type: 'application/json' });
    formData.append('post', blob);
    post.images.forEach((image) => {
      formData.append(`images`, image);
    });

    return axios.post(`${process.env.REACT_APP_API_URI}/api/posts`, formData, {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
  };


๐Ÿ“Œ ๋ฌธ์ œ 5: ํšŒ์›๊ฐ€์ž… ๋ชจ๋‹ฌ

์ž‘์„ฑ์ž: ์ž„ํ•˜๋ฃจ

๋ฌธ์ œ

ํšŒ์›๊ฐ€์ž… ๋ชจ๋‹ฌ์—์„œ step์ด ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ๊ฐ™์€ ๋ชจ๋‹ฌ ์•ˆ์—์„œ ๋‹ค๋ฅธ ํ™”๋ฉด์„ ๋ณด์—ฌ์ฃผ์–ด์•ผ ํ•˜์ง€๋งŒ, ๋ฐ์ดํ„ฐ๋Š” ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š๊ณ  ์ €์žฅ๋˜์–ด ์žˆ๋‹ค๊ฐ€ ๋งˆ์ง€๋ง‰ ์Šคํ…์—์„œ ํ•œ ๋ฒˆ์— post ์š”์ฒญํ•˜๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๋ฌธ์ œ

์‹œ๋„

step๋ณ„๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•˜๋Š” ๋ชจ๋“  ์ •๋ณด๋ฅผ,

  1. ๋กœ์ปฌ state๋กœ ๊ด€๋ฆฌ
    1. ๋ถˆํ•„์š”ํ•œ ๋ Œ๋”๋ง์ด ๊ณผ๋‹คํ•˜๊ฒŒ ๋งŽ์•„์ง
    2. step์„ ์ด๋™ํ•  ๋•Œ๋งˆ๋‹ค ์ €์žฅ๋˜์–ด์žˆ๋˜ state๊ฐ€ ์‚ฌ๋ผ์ง
  2. ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ (recoil)์— ์ €์žฅํ•˜์˜€๋‹ค๊ฐ€, ๋งˆ์ง€๋ง‰ step์—์„œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•ฉ์ณ post ํ•ด์ฃผ๊ธฐ
    1. step์„ ์ด๋™ํ•ด๋„ ์ด์ „ ์Šคํ…์—์„œ ์ž…๋ ฅํ•œ ์ •๋ณด๋Š” ๋‚จ์•„์žˆ์ง€๋งŒ, ์ „์—ญ์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜๋Š” ๋ฐ์ดํ„ฐ ์–‘์ด ๊ณผ๋„ํ•˜๊ฒŒ ๋งŽ์•„์ง€๋Š” ๋ฌธ์ œ

ํ•ด๊ฒฐ

react-hook-form์„ ์‚ฌ์šฉํ•ด์„œ ๊ด€๋ฆฌ

  • step์ด ๋‹ฌ๋ผ์ง€๋”๋ผ๋„ ์‚ฌ์šฉ์ž๋“ค์ด ์ž…๋ ฅํ•˜๋Š” ์ •๋ณด๋ฅผ ํ•˜๋‚˜์˜ form ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ
  • react-hook-form์ด ์ž์ฒด ์ œ๊ณตํ•˜๋Š” ์ตœ์ ํ™”๋ฅผ ํ†ตํ•ด ๋ถˆํ•„์š”ํ•œ ๋ฆฌ๋ Œ๋”๋ง์„ ํ”ผํ•จ
// ์ดˆ๊ธฐ form ์ •์˜
export type LoginModalFormData = {
  emailId: string;
  emailDomain: string;
  password: string;
};

export const useLoginModalForm = () => {
  const defaultValues = {
    emailId: '',
    emailDomain: '',
    password: '',
  };

  const form = useForm({
    defaultValues: {
      ...defaultValues,
    },
    mode: 'all',
  });

  return { form };
};

// form controller ์‚ฌ์šฉ
export const useLoginModalFormController = () => {
  const { control, watch } = useFormContext<LoginModalFormData>();

  const {
    field: { value: passwordValue, onChange: onChangePassword },
  } = useController({
    control: control,
    name: 'password',
  });

  // ์ดํ•˜ ์ค‘๋žต..
};


๐Ÿ“Œ ๋ฌธ์ œ 6: ๊ฒŒ์‹œ๊ธ€๋งˆ๋‹ค ๋‹จ ํ•œ ๋ฒˆ๋งŒ ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์›Œ์ฃผ๋Š” ๊ฒƒ์— ๋Œ€ํ•œ ๋ฌธ์ œ

์ž‘์„ฑ์ž: ์ž„ํ•˜๋ฃจ

๋ฌธ์ œ

  • โ€˜ํ•ด๊ฒฐ๋œ ๋ฏผ์›์ด์—์š”โ€™ ๊ฒŒ์‹œ๊ธ€์„ ํด๋ฆญํ•˜๋Š” ์ˆœ๊ฐ„, ๋ชจ๋‹ฌ์„ ๋„์›Œ ํ•ด๊ฒฐ๋œ ๊ฒŒ์‹œ๋ฌผ์ž„์„ ์•Œ๋ ค์•ผ ํ–ˆ๋˜ ์ƒํ™ฉ
  • ํ•ด๋‹น ๋ชจ๋‹ฌ์€ ๊ฒŒ์‹œ๊ธ€๋งˆ๋‹ค ๋ชจ๋‹ฌ ์ฐฝ์„ โ€˜๋‹จ ํ•œ ๋ฒˆ๋งŒโ€™ ๋„์›Œ์„œ ์•Œ๋ ค์ฃผ์–ด์•ผ ํ–ˆ์Œ

์‹œ๋„

1. alert์—ฌ๋ถ€๋ฅผ local state๋กœ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌ

์ฒ˜์Œ์—๋Š” ๋ชจ๋‹ฌ ์ฐฝ์ด alert๋˜์—ˆ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ useState๋กœ ๊ด€๋ฆฌ

๊ทธ๋Ÿฌ๋‚˜, useState๋กœ alert์—ฌ๋ถ€๋ฅผ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋˜๋ฉด ํ•ด๋‹น ํŽ˜์ด์ง€๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋  ๋•Œ state์˜ ๊ฐ’์ด ์ดˆ๊ธฐํ™”๋˜์–ด, ๋‹จ ํ•œ ๋ฒˆ๋งŒ ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์šฐ๋Š” ๊ฒƒ์ด ๋ถˆ๊ฐ€๋Šฅํ–ˆ์Œ

2. alert์—ฌ๋ถ€๋ฅผ recoil state๋กœ ๋งŒ๋“ค์–ด ๊ด€๋ฆฌ

๋‘ ๋ฒˆ์งธ ์‹œ๋„์—์„œ๋Š” ํŽ˜์ด์ง€๊ฐ€ ์–ธ๋งˆ์šดํŠธ๋˜์–ด๋„ alert์˜ ์—ฌ๋ถ€๊ฐ€ recoil๋กœ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ์–ด์„œ ๋ชจ๋‹ฌ ์ฐฝ์ด ๋‘ ๋ฒˆ ๋‚˜์˜ค๋Š” ์ƒํ™ฉ์€ ํ•ด๊ฒฐ

๊ทธ๋Ÿฌ๋‚˜ ์ด ๋ฐฉ์‹์—๋„ ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋Š”๋ฐ,

  1. ๊ฐ๊ฐ์˜ ๊ฒŒ์‹œ๋ฌผ๋งˆ๋‹ค alert ์—ฌ๋ถ€๋ฅผ ์ €์žฅํ•ด์•ผ ํ•˜๋Š”๋ฐ, ๋‹จ์ง€ ํ•œ ๋ฒˆ ์ด์ƒ alert ๋˜๋ฉด atom๊ฐ’์ด true๋กœ ๋ฐ”๋€Œ์–ด ๊ฒŒ์‹œ๋ฌผ๋“ค์„ ๊ตฌ๋ถ„ํ•˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ
  2. ๋กœ๊ทธ์ธ, ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ ๊ตฌํ˜„์‹œ window.location.reload()๋กœ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋Š”๋ฐ, ์ด ๊ฒฝ์šฐ recoil ๊ฐ’์ด ์ดˆ๊ธฐํ™”๋˜๋Š” ๋ฌธ์ œ

์ฆ‰ โ€˜๊ฒŒ์‹œ๋ฌผ๋งˆ๋‹คโ€™ alert๋ฅผ ๋„์› ๋Š”์ง€ ์•ˆ ๋„์› ๋Š”์ง€ ์—ฌ๋ถ€๋ฅผ ๊ฐ๊ฐ ์ €์žฅํ•˜๊ณ , โ€˜์ƒˆ๋กœ๊ณ ์นจ๋˜์–ด๋„โ€™ ๊ฐ’์ด ๋‚จ์•„์žˆ์–ด์•ผ ํ–ˆ์Œ

ํ•ด๊ฒฐ

  • ํ˜„์žฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋Š” ๋กœ์ง์ด ๋งŽ์ด ์žˆ์–ด recoil๋งŒ์œผ๋กœ๋Š” ์›ํ•˜๋Š” ์ƒํƒœ๋ฅผ ์˜์†์ ์œผ๋กœ ์ €์žฅํ•  ์ˆ˜ ์—†์Œ
  • ์ด๋ฅผ atomFamily์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒŒ์‹œ๊ธ€ ID๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•ด ๋™์ ์œผ๋กœ atom์„ ์ƒ์„ฑ
  • sessionStorage๋ฅผ ์‚ฌ์šฉํ•œ ์ด์œ 
    • ๊ฒŒ์‹œ๊ธ€ ID๋‹น ๋ชจ๋‹ฌ์ด ์—ด๋ ธ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” ์žฅ๊ธฐ์ ์œผ๋กœ ์ €์žฅ๋  ํ•„์š”๊ฐ€ ์—†์Œ. ์ฆ‰ ์ด๋Š” ์ž„์‹œ์ ์ธ ๋ฐ์ดํ„ฐ์ด๋ฏ€๋กœ sessionStorage๊ฐ€ ์ ํ•ฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐ
    • ์‚ฌ์šฉ์ž๊ฐ€ ํƒญ์„ ๋‹ซ์œผ๋ฉด ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๋Š” ๊ฒƒ์ด ๊ตฌํ˜„ ๋ชฉ์ ์— ๋” ์ ํ•ฉํ•˜๋‹ค๊ณ  ์ƒ๊ฐ
// ์ •์˜

import { atomFamily } from 'recoil';
import { recoilPersist } from 'recoil-persist';

const { persistAtom } = recoilPersist({
  key: 'isDoneAlerted',
  storage: sessionStorage,
});

export const $isDoneAlertedFamily = atomFamily({
  key: 'isDoneAlertedFamily',
  default: false,
  effects_UNSTABLE: (postId) => [
    ({ setSelf, onSet }) => {
      const storedValue = sessionStorage.getItem(
        `isDoneAlertedFamily_${String(postId)}`,
      );
      if (storedValue != null) {
        setSelf(JSON.parse(storedValue));
      }

      onSet((newValue) => {
        sessionStorage.setItem(
          `isDoneAlertedFamily_${String(postId)}`,
          JSON.stringify(newValue),
        );
      });
    },
  ],
});

-----

//์‚ฌ์šฉ

const [isDoneAlerted, setisDoneAlerted] = useRecoilState(
    $isDoneAlertedFamily(postId),
  );

...

useEffect(() => {
    if (localDoneCount === 5 && !isDoneAlerted) {
      setIsReallyDone(true);
      setisDoneAlerted(true);
      openModal(EModalType.POP_UP, {
        title: 'ํ•ด๊ฒฐ ์™„๋ฃŒ ์ฒ˜๋ฆฌ๋œ ๊ฒŒ์‹œ๋ฌผ์ž…๋‹ˆ๋‹ค',
        cancelButton: false,
        functionButton: {
          label: '๋‹ซ๊ธฐ',
          onClick: () => {
            closeModal();
          },
          theme: 'emptyBlue',
        },
      });
    }
}, [localDoneCount, isReallyDone]);