_/Velog

๐ŸŽฅ React๋กœ ์œ ํŠœ๋ธŒ ํด๋ก ์ฝ”๋”ฉ ๐Ÿ“น

์„ ๋‹ฌ 2022. 3. 31. 20:39
๋ฐ˜์‘ํ˜•

 

 


๐Ÿ–ผ Outline

 

 

Why - ๊ต๋‚ด์›น๊ฐœ๋ฐœ๋™์•„๋ฆฌ์—์„œ ๊ฒจ์šธ๋ฐฉํ•™ ํ† ์ดํ”„๋กœ์ ํŠธ๋กœ ์ง„ํ–‰
What - ์œ ํŠœ๋ธŒ ์›น์‚ฌ์ดํŠธ ํด๋ก ์ฝ”๋”ฉ
Who - spring์„ ์‚ฌ์šฉํ•˜์‹œ๋Š” ๋‘๋ช…์˜ ๋ฐฑ์—”๋“œ ๋ถ„๋“ค๊ณผ ํ˜‘์—…ํ•˜์˜€์œผ๋ฉฐ ๋‚˜๋Š” ํ”„๋ก ํŠธ๋ฅผ ๋งก์•˜๋‹ค
How - React ๊ธฐ๋ฐ˜
When - ์•ฝ ํ•œ ๋‹ฌ(2021๋…„ 11์›” 08์ผ ~ 2021๋…„ 12์›” 4์ผ)๊ฐ„ ์ง„ํ–‰ํ–ˆ๋‹ค

 


โœจ Main Features

 

1. ๋ฐ˜์‘ํ˜•

2ํ•™๊ธฐ์— ์ง‘์ค‘์ ์œผ๋กœ ๊ณต๋ถ€ํ•œ ๋ฐ˜์‘ํ˜•์„ ๋ณธ ํ”„๋กœ์ ํŠธ ๊ณณ๊ณณ์—์„œ ๊ตฌํ˜„ํ•˜๋ ค ๋…ธ๋ ฅํ–ˆ๋‹ค.

 

๋‹น์—ฐํ•˜์ง€๋งŒ? ๋ฏธ๋””์–ด ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•˜์—ฌ css ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜์˜€๊ณ 

 

const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: 70vw;

  @media (max-width: 1300px) {
    width: 80vw;
  }
  @media (max-width: 800px) {
    width: 80vw;
  }
`;

 

์‚ฌ์ด๋“œ๋ฉ”๋‰ด๋‚˜ ํ—ค๋” ๊ฐ™์€ ํ™”๋ฉด ๊ตฌ์„ฑ๋“ค์€ ํฌ๊ฒŒ 3๋‹จ๊ณ„(๋ชจ๋ฐ”์ผ๋ทฐ, ์•„์ดํŒจ๋“œ๋ฐํƒญ, pc๋ทฐ)๋กœ ๋‚˜๋ˆ ์„œ ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ ๋ฐฉ์‹์„ ๋ฐ”๊ฟจ๋‹ค

 

ํƒ์ƒ‰ํƒญ ๋ฒ„ํŠผ๋“ค๊ณผ ๋ฉ”์ธํƒญ ๋™์˜์ƒ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ๋ฐฐ์—ด ๋ฐ ์‚ฌ์ด์ฆˆ๋Š” ํ™”๋ฉด ํฌ๊ธฐ์— ๋”ฐ๋ผ ์—ฐ์†์ ์œผ๋กœ ์กฐ์ ˆ๋˜๋„๋ก ์„ค์ •ํ•˜์˜€๋‹ค

 


 

2. ๋งˆ์šฐ์Šค ์˜ฌ๋ ธ์„ ๋•Œ ๋™์˜์ƒ ์žฌ์ƒ

์‹ค์ œ ์œ ํŠœ๋ธŒ ์›น์—์„œ๋Š” ๊ฐ ๋™์˜์ƒ ์ปดํฌ๋„ŒํŠธ๋“ค์˜ ์ธ๋„ค์ผ์ด ์ •์ง€๋˜์–ด์žˆ๊ณ 
๋งˆ์šฐ์Šค๋ฅผ ๊ทธ ์œ„์— ์˜ฌ๋ ธ์„ ๋•Œ ๋™์˜์ƒ์ด ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ˜•์‹์œผ๋กœ ์ž๋™์œผ๋กœ ์žฌ์ƒ๋œ๋‹ค

 

 

์ด๋ฒˆ ์œ ํŠœ๋ธŒ ํด๋ก ์ฝ”๋”ฉ์—์„œ๋Š” ํ•ด๋‹น ๊ธฐ๋Šฅ์— ์ง‘์ค‘ํ–ˆ๋‹ค.


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

 

 

2-1. ์žฌ์ƒ๋˜๋Š” ๋™์˜์ƒ ์ปดํฌ๋„ŒํŠธ

์šฐ์„  ๋ณธ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด "์žฌ์ƒ๋˜๋Š”" ๋™์˜์ƒ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ•„์š”ํ–ˆ๋‹ค.
๊ฐ์‚ฌํ•˜๊ฒŒ๋„ url์„ ๋„ฃ์œผ๋ฉด ํ•ด๋‹น ๋น„๋””์˜ค๋ฅผ ์žฌ์ƒ์‹œ์ผœ์ฃผ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜คํ”ˆ์†Œ์Šค๋กœ ์ž˜ ๋งŒ๋“ค์–ด์ ธ ์žˆ์–ด ๊ฐ์‚ฌํžˆ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

ReactPlayer Component

https://www.npmjs.com/package/react-player

๋ฐ˜์‘ํ˜•
 

react-player

A React component for playing a variety of URLs, including file paths, YouTube, Facebook, Twitch, SoundCloud, Streamable, Vimeo, Wistia and DailyMotion. Latest version: 2.14.1, last published: 3 days ago. Start using react-player in your project by running

www.npmjs.com

 

๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ ์œ ์šฉํ•œ props๋“ค๋„ ๋งŽ์ด ์žˆ์—ˆ๋Š”๋ฐ,

 

playing="true" ๋กœ ์žฌ์ƒ ์—ฌ๋ถ€๋„ ๊ฒฐ์ • ํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ 
muted="true" ๋กœ ๋™์˜์ƒ์„ ์กฐ์šฉํžˆ ์žฌ์ƒ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ฒŒ ์ปค์Šคํ…€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

<ReactPlayer url={data.videoUrl} muted="true" playing="true" />

 

 

2-2. ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„๋ง react๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ

์ด์ œ ๋™์˜์ƒ ์žฌ์ƒ์€ ์ค€๋น„ ๋˜์—ˆ์œผ๋‹ˆ ๋งˆ์šฐ์Šค๋ฅผ ์˜ฌ๋ ธ์„๋•Œ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ณด์ด๊ฒŒ ๋งŒ๋“ค๋ฉด ๋˜๋Š”๋ฐ..

 

๋ฌธ์ œ๋Š” ๋ฉ”์ธํŽ˜์ด์ง€ ํƒญ๋ฐ” ๋ฒ„ํŠผ์ด๋‚˜ ์‚ฌ์ด๋“œ๋ฐ” ๋ฉ”๋‰ด ๋ฒ„ํŠผ๋“ค์˜ ์ƒ‰์ƒ ๋ณ€ํ™”๋ฅผ ๊ตฌํ˜„ํ•  ๋–„ ์‚ฌ์šฉํ–ˆ๋˜

:hover ๊ธฐ๋Šฅ์€ css๋กœ ํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํƒ€์ผ ๋ณ€ํ™”๋งŒ ์ ์šฉ์ด ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฒƒ์ด์—ˆ๋‹ค.

 

์—ฌ๊ธฐ์„œ๋Š” ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„๋ง์— ๋”ฐ๋ผ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ณ€ํ™”ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ํ•„์š”ํ–ˆ๋‹ค.

 

 

์ด์— useState()๋ฅผ ์ด์šฉํ•˜์—ฌ ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„๋ง ์—ฌ๋ถ€๋ฅผ ์ƒํƒœ๋กœ์จ ๊ด€๋ฆฌํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

const [hover, setHover] = useState(false);

 

 

์ดํ›„ onMouseOver ์™€ onMouseOut ์†์„ฑ์„ ์ด์šฉํ•ด

๋งˆ์šฐ์Šค๊ฐ€ ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ์— ์˜ฌ๋ผ์™€์žˆ๋Š”๊ฐ€๋ฅผ ํŒ๋‹จํ•˜์—ฌ ์ƒํƒœ๋ฅผ set ํ•ด์คฌ๋‹ค

<VideoItem
      onMouseOver={() => setHover(true)}
      onMouseOut={() => setHover(false)}
    >
      {hover ? (
        <ReactPlayer url={data.videoUrl} />
      ) : (
        <Thumbnail src={data.videoThumbnail} />
      )}

 

 

๊ฒฐ๋ก ์ ์œผ๋กœ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋น„๋””์˜ค ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ ์™„์„ฑ!

<VideoItem
      onMouseOver={() => setHover(true)}
      onMouseOut={() => setHover(false)}
    >
      {hover ? (
        <ReactPlayer
          url={data.videoUrl}
          muted="true"
          width="100%"
          height="auto"
          playing="true"
          style={{ marginBottom: "10px" }}
        />
      ) : (
        <Thumbnail src={data.videoThumbnail} />
      )}

      <Profile src={youtubeData["data"][index + 28].channelThumbnail} />
      <Info>
        <Title>{data.videoTitle}</Title>
        <Chanel>{data.videoChannel}</Chanel>
        <Views>์กฐํšŒ์ˆ˜ {data.videoCount}ํšŒ·</Views>
        <Date>{relativeDate()}</Date>
      </Info>
    </VideoItem>

 


3. ๋กœ๋”ฉํ™”๋ฉด ๊ตฌํ˜„

ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋นŒ๋“œํ•  ๋•Œ๋งˆ๋‹ค ๋ณด์ด๋Š” ํฐ์ƒ‰ ํ™”๋ฉด์ด ๋„ˆ๋ฌด ๊ฑฐ์Šฌ๋ ธ๋‹ค.

 

์ด์— ์•ผ๋งค์˜€์ง€๋งŒ ์‹ค์ œ ์œ ํŠœ๋ธŒ ๋กœ๋”ฉ ํ™”๋ฉด๊ณผ ์œ ์‚ฌํ•ด๋ณด์ด๊ฒŒ ๋ˆˆ์†์ž„์šฉ์œผ๋กœ๋ผ๋„ ๋กœ๋”ฉํ™”๋ฉด์„ ๋งŒ๋“ค์–ด๋ณด๊ธฐ๋กœ ํ–ˆ๊ณ ,

๋งˆ์นจ 2ํ•™๊ธฐ๋•Œ ๋‚ด๊ฐ€ ๋งก์€ ์„ธ๋ฏธ๋‚˜ ๋ถ€๋ถ„์ด ๊ณ ๊ธ‰CSS ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ถ€๋ถ„์ด์˜€๋Š”๋ฐ,

๋กœ๋”ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์‹ค์Šต ์ž๋ฃŒ๋ฅผ ๋งŒ๋“ค๋ฉฐ ๋กœ๋”ฉ์ฐฝ์„ ๋Œ€์ถฉ ๋‹ค๋ฃฌ์ ์ด ์žˆ์—ˆ๋‹ค

 

 

์šฐ์„  ๋กœ๋”ฉํ™”๋ฉด์— ๋ฐ˜๋ณต์ ์œผ๋กœ ๋‚˜์˜ฌ ํšŒ์ƒ‰ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ตฌํ˜„ํ•˜๊ณ 
(์ €๊ธฐ ์ œ๋ชฉ๋ถ€๋ถ„์ด๋ž‘ ์ปจํ…์ธ  ๊ธธ์ด๊ฐ€ ๋‹ค๋ฅธ๊ฑฐ ์ผ๋ถ€๋Ÿฌ ๊ทธ๋Ÿฐ๊ฑฐ๋‹ค.. ์ง„์งœ ์ €๋Ÿผ..)
(์‚ด๋ฉด์„œ ๋กœ๋”ฉํ™”๋ฉด ์บก์ณํ•ด๋†“๊ณ  ์ƒ‰์ƒ ์ถ”์ถœํ•˜๊ณ  ์‚ฌ์ด์ฆˆ ์žฌ๊ณ  ๊ทธ๋Ÿฐ๊ฑด ์ฒ˜์Œ..)

 

 

์ดํ›„ useState() ๋กœ ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌ

const [loading, setLoading] = useState(false);

 

๊ทธ๋ฆฌ๊ณ  useEffect() ๋ฅผ ์ด์šฉํ•˜์—ฌ ์„œ๋ฒ„๋กœ ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋™์•ˆ์€ true,

๋ฐ›์•„์˜ค๋Š”๊ฑธ ์™„๋ฃŒํ•œ ํ›„์—๋Š” false ๋กœ ๋กœ๋”ฉ์ƒํƒœ๋ฅผ set ํ•ด์ฃผ์—ˆ๋‹ค

useEffect(() => {
    const fetchData = async () => {
      setLoading(true);
      try {
        const response = await axios.get(
          `http://${baseurl}/video/get/${index}`
        );
        setData(response.data);
        console.log(data);
      } catch (e) {
        console.log(e);
      }
      setLoading(false);
    };
    fetchData();
  }, []);

  if (loading) {
    return <Loading />;
  }

  if (!data) {
    return <Loading />;
  }

 

๊ทธ๋ ‡๊ฒŒ ์™„์„ฑ-!

 


๐Ÿ“ Structure

 

1. ํด๋” ๊ตฌ์กฐ

ํด๋” ๊ตฌ์กฐ๋Š” ์šฉ๋„์— ๋”ฐ๋ผ ํฌ๊ฒŒ ๋„ค๊ฐ€์ง€๋กœ ๋‚˜๋ˆ„๊ณ  ๊ฐ ํด๋” ๋‚ด๋ถ€์— ํŽ˜์ด์ง€๋ณ„ ํด๋”๋ฅผ ๋˜ ๋งŒ๋“ค์–ด์„œ ํŒŒ์ผ๋“ค์„ ๊ด€๋ฆฌํ•ด์คฌ๋‹ค.
์ด๋ฒˆ์—๋Š” ํ˜ผ์ž ์ž‘์—…ํ•œ ๋•๋ถ„์— ์˜จ์ „ํžˆ ๋‚ด ์ทจํ–ฅ๋Œ€๋กœ ํด๋”๋ฅผ ๊ตฌ๋ถ„ํ–ˆ๋Š”๋ฐ ๋‚˜๋Š” ์ด๊ฒŒ ์ •๋ง ํŽธํ–ˆ๋‹คใ…Žใ…Ž

 

  • assets
    : ์ด๋ฏธ์ง€ ๋ฐ ์•„์ด์ฝ˜ ์—์…‹๋“ค
  • pages
    : ํŽ˜์ด์ง€๋ณ„ js ํŒŒ์ผ๋“ค
  • component
    : ๊ฐ ํŽ˜์ด์ง€์— ์‚ฌ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค
  • shared
    : ๋ชจ๋“  ํŽ˜์ด์ง€์—์„œ ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋“ค

 

๋”๋ณด๊ธฐ

App.js

  • App.css
  • index.js
  • assets
    • assets_header - ํ—ค๋”์— ์‚ฌ์šฉํ•œ ์ด๋ฏธ์ง€ ์•„์ด์ฝ˜ ์—์…‹ ๋ชจ์Œ
    • card - ํƒ์ƒ‰ ํŽ˜์ด์ง€ ์นด๋“œ ์ด๋ฏธ์ง€์—์…‹
    • icon - ์‚ฌ์ด๋“œ๋ฐ”์— ์‚ฌ์šฉํ•œ ์•„์ด์ฝ˜ ์—์…‹
  • pages
    • Finder.js
    • Main.js
    • Subscribe.js
  • component - ๊ฐ ํŽ˜์ด์ง€์— ์‚ฌ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ๋“ค
    • Finder
      • LargeVideo.js
    • Main
      • Ad.js
      • Filter.js
      • Loading.js
      • Video.js
      • VideoList.js
    • Subscribe
      • LargeVIdeoWithChannel.js
    • loading.png
  • shared - ๋ชจ๋“  ํŽ˜์ด์ง€์— ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉํ•œ ์ปดํฌ๋„ŒํŠธ
    • Header.js
    • Layout.js
    • Sidebar.js

 

2. ๋ ˆ์ด์•„์›ƒ ๊ตฌ์„ฑ

๋งˆ์ง€๋ง‰์— ์žˆ๋Š” shared ์—์„œ Layout.js ๊ฐ€ ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐ˜๋ณต์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€ ํŒŒ์ผ์ด๋‹ค.

function Layout({ active, content }) {
  return (
    <Wrapper>
      <Header />
      <Body>
        <Sidebar active={active} />
        <div className="contents">{content}</div>
      </Body>
    </Wrapper>
  );
}

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ณตํ†ต๋œ ์ปดํฌ๋„ŒํŠธ์ธ ํ—ค๋”์™€ ์‚ฌ์ด๋“œ๋ฐ”์˜ ๋ ˆ์ด์•„์›ƒ์„ ๋ณธ Layout.js์—์„œ ์žก์•„์ฃผ๊ณ 

๋‚ด์šฉ์— ํ•ด๋‹นํ•˜๋Š” ๋ถ€๋ถ„์€ content ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฐ›์•„์™€์„œ ๋ณด์—ฌ์ฃผ๊ฒŒ ํ–ˆ๋‹ค.

 

์ฆ‰, ํ™ˆํƒญ, ํƒ์ƒ‰ํƒญ, ๊ตฌ๋…ํƒญ์— ๋”ฐ๋ผ ๋‹ฌ๋ผ์ง€๋Š” ๋‚ด์šฉ ๋ถ€๋ถ„์€ content ์ปดํฌ๋„ŒํŠธ๋กœ ๋ฐ›์•„์˜ค๋Š” ํ˜•์‹

 

App.js ์—์„œ๋Š” ๊ฒฝ๋กœ์— ๋”ฐ๋ผ Layout ์ปดํฌ๋„ŒํŠธ์˜ content ์ปดํฌ๋„ŒํŠธ๋งŒ ๋ฐ”๊ฟ”์„œ ๋ณด์—ฌ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋œ๋‹ค.

function App() {
  return (
    <Router>
      <Routes>
        <Route
          path="/"
          exact={true}
          element={<Layout active="main" content={<Main />} />}
        />
        <Route
          path="/Finder"
          element={<Layout active="finder" content={<Finder />} />}
        />
        <Route
          path="/Subscribe"
          element={<Layout active="subscribe" content={<Subscribe />} />}
        />
      </Routes>
    </Router>
  );
}

 


๐Ÿšช Closing

์•„๋ฌดํŠผ ์ด๋ ‡๊ฒŒ ๊ธธ๋ฉด ๊ธธ๊ณ  ์งง์œผ๋ฉด ์งง์€ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋์ด ๋‚ฌ๋‹ค..!

 

Github

https://github.com/seondal/Clone-Youtube-Web

 

GitHub - seondal/clone-youtube-web: clone YouTube Web with React ๐Ÿบ ๐Ÿ“น

clone YouTube Web with React ๐Ÿบ ๐Ÿ“น. Contribute to seondal/clone-youtube-web development by creating an account on GitHub.

github.com

 

์‚ฌ์‹ค ํŒ€์— ๋ฐฐ์ •๋œ ํ”„๋ก ํŠธ ๋‹ด๋‹น์ด ๋‘๋ช…์ด์˜€๋Š”๋ฐ,

๊ฐ™์€ ํŒ€์ด์…จ๋˜ ํ”„๋ก ํŠธ ์„ ๋ฐฐ๋‹˜๊ป˜์„œ ์ง์žฅ ๋•Œ๋ฌธ์— ๋ฐ”๋น ์ง€์…”์„œ ํ”„๋ก ํŠธ๋ถ€๋ถ„์€ ๋‚˜ ํ™€๋กœ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

์ด๋•Œ ๋ฐ˜์‘ํ˜• ์—ฐ์Šตํ•œ ๊ฑธ๋กœ ์ดํ›„์— ์›น์‚ฌ์ดํŠธ ์™ธ์ฃผ๊นŒ์ง€ ํ•˜๊ฒŒ ๋˜์—ˆ์œผ๋‹ˆ ๊ฒฐ๊ณผ์ ์œผ๋กœ๋Š” ์ •๋ง ์ข‹์€ ๊ฒฝํ—˜์ด์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค :)

 

Instagram

https://www.instagram.com/p/CXSYOIiBctm/?utm_source=ig_web_copy_link

๋ฐ˜์‘ํ˜•