React

 

 

초급자를 위해 준비한
[웹 개발, 프론트엔드] 강의입니다.

리액트를 가장 쉽고 빠르게 배울 수 있는 리액트 입문 강의입니다. 토이 프로젝트를 만들면서 개념과 실전 감각을 익히고 배포까지 3시간 만에 완성하는 알짜 강의! 초급, 중급, 고급으로 구성된 시리즈의 첫 번째 강의입니다 :)

✍️
이런 걸
배워요!

리액트 개발

프런트엔드 개발

웹 개발

나만의 토이프로젝트

백엔드 연동

 

강의 :

https://www.inflearn.com/course/만들면서-배우는-리액트-기초#

 

 

 

https://github.com/milooy/cat-jjal-maker/blob/main/answers/2-setup.html

 

 

 

 

[1. 인트로]


1 .강의 소개

강의 :

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101627&category=questionDetail&tab=curriculum

 

 

2 .프로젝트 개발환경 설정하기 - VSCode, GiHub

강의 :

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101629&category=questionDetail&tab=curriculum

 

format on save 

 

 

 

3. 프로젝트 안내

강의 :

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101630&category=questionDetail&tab=curriculum

 

 

 

 

 

 

 

 

[2. 리액트가 왜 좋은가요?]

 


4. 웹사이트 움직이게 만들기

강의 :

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101631&category=questionDetail&tab=curriculum

 

 

 

5.리액트 맛보기

강의 :

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101632&category=questionDetail&tab=curriculum

 

 

중요!!!

리액트 18을 쓰시는 분들은 render라는 문법 대신 createRoot를 사용해주세요 :)

const 여기다가그려 = document.querySelector("#app");



// 리액트17 이하면?

ReactDOM.render(catItem, 여기다가그려);



// 리액트18 이면?

ReactDOM.createRoot(여기다가그려).render(catItem);

 

 

 

 

고양이 가라사대

 

1번째 고양이 가라사대

생성

고양이????

 

 

 

 

 

 

 

 

6. Babel

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101633&category=questionDetail&tab=curriculum

 

 

https://babeljs.io/repl

 

바벨은 다음과 같이 head  에 브라우저가 인식할 수있게 js 코드를 새롭게 그려 준다

 

 

 

 

 

 

 

 7. JSX 로 HTML과 javascript 짬뽕하기

 

 

강의 :

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101634&category=questionDetail&tab=curriculum

 

 

index.html

 

고양이 가라사대

 

 

 

 

 

 

 

 

 

 

 

 

 

8. 퀴즈풀이

 

강의 :

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101635&category=questionDetail&tab=curriculum

 

 

 

 

 

 

 

[3. 리액트 앱 바닥부터 만들기]

 

9. 컴포넌트가 뭔가요?

 

강의 :

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101637&category=questionDetail&tab=curriculum

 

 

 

 

 

 

 

10. 컴포넌트 만들기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101801&category=questionDetail&tab=curriculum

 

index.html

 

고양이 가라사대

 

 

 

 

 

 

 

 

 

 

11. 컴포넌트 퀴즈풀이

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101802&category=questionDetail&tab=curriculum

 

 

 

 

 

 

12. 컴포넌트 퀴즈풀이

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101803&category=questionDetail&tab=curriculum

 

 

 

 

스타일 주소

 

https://github.com/milooy/cat-jjal-maker/blob/main/answers/12-styling.html

 

 

~
      const MainCard=({src,alt,width })=>{
        return  (<div className="main-card">
          <img
            src={src}
            alt={alt}
            width={width}
          />
          <button>????</button>
        </div>
        );
      }

~

      function Favorites(){
         return (
            <ul className="favorites">
              <CatItem img="http://placeimg.com/640/480/arch"  title="cat1" />
              <CatItem img="http://placeimg.com/640/480/nature" title="cat2" />
              <CatItem  img="http://placeimg.com/640/480/animals" title="cat3"/>
          </ul>
         )
      }
      

 

 

 

 

 

 

 

 

 

 

13. 이벤트 다루기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101804&category=questionDetail&tab=curriculum

 

 

 

 

~  

 const Form=()=>{
      function handleFormSubmit(e){
        e.preventDefault();
        console.log("폼 전송 됨");
      }

        return (
          <form onSubmit={handleFormSubmit}>
            <input type="text" name="name" placeholder="영어 대사를 입력해주세요" />
            <button type="submit">생성</button>
          </form>
        );
    }


  const MainCard=({src,alt,width })=>{
        function handleHeartClick(){
          console.log("하트 눌렀음");
        }
        function handleHeartMouseOver(){
          console.log("하트 스쳐 지나감");
        }

        return  (
          <div className="main-card">
          <img
            src={src}
            alt={alt}
            width={width}
          />
          <button onClick={handleHeartClick}
            onMouseOver={handleHeartMouseOver}
          >????</button>
        </div>
        );
      }




~



 

 

 

 

 

 

 

14. useState로 상태 만들기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101805&category=questionDetail&tab=curriculum

 

    const Form=()=>{
      const [counter ,setCounter ] =React.useState(1);
      console.log("카우터" , counter);

      function handleFormSubmit(e){
        e.preventDefault();
        setCounter(counter+1);
        console.log("폼 전송 됨" ,counter);
      }

        return (
          <form onSubmit={handleFormSubmit}>
            <input type="text" name="name" 
              placeholder="영어 대사를 입력해주세요" />
            <button type="submit">생성</button>
          </form>
        );
    }
 

 

 

 

 

 

 

 

 

 

 

15. 상태끌어올리기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101806&category=questionDetail&tab=curriculum

 

~

    const Form=({handleFormSubmit})=>{
        return (
          <form onSubmit={handleFormSubmit}>
            <input type="text" name="name" 
              placeholder="영어 대사를 입력해주세요" />
            <button type="submit">생성</button>
          </form>
        );
    }
 


~


      const App=()=>{
        const [counter ,setCounter ] =React.useState(1);
          console.log("카우터" , counter);

        function handleFormSubmit(e){
          e.preventDefault();
          setCounter(counter+1);
          console.log("폼 전송 됨" ,counter , e.target.name.value);
        }


        return(
          <div>
            <Title>1번째 고양이 가라사대</Title>,
            <Form  handleFormSubmit={handleFormSubmit} />
            <MainCard src="https://source.unsplash.com/random"   alt="alt"   width="400" />
            <Favorites  />
          </div>

        )

      }


~

 

 

 

 

 

 

 

 

 

16. useState 퀴즈풀이

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101807&category=questionDetail&tab=curriculum

 

    const mainImageSrcArr = [ "https://cataas.com/cat/HSENVDU4ZMqy7KQ0/says/react",
        "https://cataas.com/cat/BxqL2EjFmtxDkAm2/says/inflearn",
        "https://cataas.com/cat/18MD6byVC1yKGpXp/says/JavaScript",
        "https://source.unsplash.com/random",
        "http://placeimg.com/640/480/arch",
        "http://placeimg.com/640/480/nature",
        "http://placeimg.com/640/480/animals"
    ]






~

 const MainCard=({mainCardImgSrc,alt,width })=>{
        function handleHeartClick(){
          console.log("하트 눌렀음");
        }
        function handleHeartMouseOver(){
          console.log("하트 스쳐 지나감");
        }

        return  (
          <div className="main-card">
          <img
            src={mainCardImgSrc}
            alt={alt}
            width={width}
          />
          <button onClick={handleHeartClick}  onMouseOver={handleHeartMouseOver}   >????</button>
        </div>
        );
      }


~

 const App=()=>{
        const [counter ,setCounter ] =React.useState(1);
        const [mainCardImgSrc, setMainCardImgSrc] =React.useState(mainImageSrcArr[0]);

        function handleFormSubmit(e){
          e.preventDefault();
          setCounter(counter+1);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);

          setMainCardImgSrc(mainImageSrcArr[nIndex]);
          console.log("폼 전송 됨" ,counter , e.target.name.value);
        }


        return(
          <div>
            <Title>1번째 고양이 가라사대</Title>,
            <Form  handleFormSubmit={handleFormSubmit} />
            <MainCard mainCardImgSrc={mainCardImgSrc}   alt="alt"   width="400" />
            <Favorites  />
          </div>

        )

      }


~





 

 

 

 

 

 

 

 

17. 리스트

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101808&category=questionDetail&tab=curriculum

 

~

      function Favorites(){
        const CAT1 = "https://cataas.com/cat/HSENVDU4ZMqy7KQ0/says/react";
        const CAT2 = "https://cataas.com/cat/BxqL2EjFmtxDkAm2/says/inflearn";
        const CAT3 = "https://cataas.com/cat/18MD6byVC1yKGpXp/says/JavaScript";
        const cats=[CAT1,CAT2];

         return (
            <ul className="favorites">
              {
                cats.map(cat=>(
                  <CatItem img={cat} key={cat} />
                ))
              }
          </ul>
         )
      }

~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

18.배웠던 개념 조합해서 기능 추가 (상태, prop, 이벤트, 리스트)

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101809&category=questionDetail&tab=curriculum

 

 

     function Favorites({favorites}){
        console.log(" favorites   " ,favorites);

         return (
            <ul className="favorites">
              {
                favorites.map(cat=>(
                  <CatItem img={cat} key={cat} />
                ))
              }
          </ul>
         )
      }
      
      const App=()=>{
        const CAT1 = "https://cataas.com/cat/HSENVDU4ZMqy7KQ0/says/react";
        const CAT2 = "https://cataas.com/cat/BxqL2EjFmtxDkAm2/says/inflearn";
        const CAT3 = "https://cataas.com/cat/18MD6byVC1yKGpXp/says/JavaScript";

        const cats= [CAT1,CAT2];

        const [favorites , setFavorites]=React.useState(cats);
        const [counter ,setCounter ] =React.useState(1);
        const [mainCardImgSrc, setMainCardImgSrc] =React.useState(mainImageSrcArr[0]);

        function handleFormSubmit(e){
          e.preventDefault();
          setCounter(counter+1);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);

          setMainCardImgSrc(mainImageSrcArr[nIndex]);
          console.log("폼 전송 됨" ,counter , e.target.name.value);
        }

        function handleHeartClick(){
          console.log("하트 눌렀음");
          setFavorites([...favorites, CAT3])
        }

        return(
          <div>
            <Title>1번째 고양이 가라사대</Title>,
            <Form  handleFormSubmit={handleFormSubmit} />
            <MainCard mainCardImgSrc={mainCardImgSrc}  handleHeartClick={handleHeartClick} alt="alt"   width="400" />
            <Favorites  favorites={favorites} />
          </div>

        )

      }

 

 

 

 

 

 

19.폼 다루기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101810&category=questionDetail&tab=curriculum

 

    const Form=({handleFormSubmit})=>{
       const [value, setValue] =React.useState("");

       function handleInputChange(e){
         setValue(e.target.value.toUpperCase()) ;
       }
        return (
          <form onSubmit={handleFormSubmit}>
            <input type="text" name="name"   placeholder="영어 대사를 입력해주세요"  
                value={value}
                onChange={handleInputChange}
            />
             
            <button type="submit">생성</button>
          </form>
        );
    }

 

 

 

 

 

 

 

 

20.폼 검증하기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101811&category=questionDetail&tab=curriculum

 

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>고양이 가라사대</title>
  </head>
  <style>
    body {
      text-align: center;
    }
    .main-card button {
      position: relative;
      left: -45px;
      bottom: 15px;
    }
    .favorites {
      list-style: none;
      display: flex;
      justify-content: center;
      flex-wrap: wrap;
      gap: 15px;
    }
    .favorites img {
      width: 150px;
    }
    form{
      margin-bottom: 20px;
    }
  </style>
  <body>
     
    <div id="app"></div>


     <!-- React를 실행. -->
    <!-- 주의: 사이트를 배포할 때는 "development.js"를 "production.min.js"로 대체하세요. -->
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

    
    <script type="text/babel">
    const mainImageSrcArr = [ "https://cataas.com/cat/HSENVDU4ZMqy7KQ0/says/react",
        "https://cataas.com/cat/BxqL2EjFmtxDkAm2/says/inflearn",
        "https://cataas.com/cat/18MD6byVC1yKGpXp/says/JavaScript",
        "https://source.unsplash.com/random",
        "http://placeimg.com/640/480/arch",
        "http://placeimg.com/640/480/nature",
        "http://placeimg.com/640/480/animals"
    ]
  
 
     const Title= (props)=>(
        <h1>{props.children}</h1>
     )


    const Form=({counter , setCounter, setMainCardImgSrc })=>{
      
       const includesHangul = (text) => /[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/i.test(text);
       const [value, setValue] =React.useState("");
       const [errorMessage  , setErrorMessage] =React.useState("");


       function handleInputChange(e){
          const useValue=e.target.value;
          setErrorMessage("");
          if(includesHangul(useValue)){
            setErrorMessage("한글은 입력할 수 없습니다.");
          }
          setValue(e.target.value.toUpperCase()) ;
       }

       function handleFormSubmit(e){
          e.preventDefault();
          setErrorMessage("");
          if(value===''){
            setErrorMessage("빈값으로 만들 수 없습니다.");
          }
          setCounter(counter+1);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);
          setMainCardImgSrc(mainImageSrcArr[nIndex]);
          console.log("폼 전송 됨" ,counter , e.target.name.value);
        }

        return (
          <form onSubmit={handleFormSubmit}>
            <input type="text" name="name"   placeholder="영어 대사를 입력해주세요"  
                value={value}
                onChange={handleInputChange}
            />
             
            <button type="submit">생성</button>
            <p style={{color:"red"}}>{errorMessage}</p>
          </form>
        );
    }
 
      const MainCard=({mainCardImgSrc, handleHeartClick ,alt,width })=>{


        return  (
          <div className="main-card">
          <img
            src={mainCardImgSrc}
            alt={alt}
            width={width}
          />
          <button onClick={handleHeartClick}   >????</button>
        </div>
        );
      }

      function CatItem(props){
        return (
             <li>
               <img src={props.img} style={{width:"150px"}} />
              </li>
        );
      }

      function Favorites({favorites}){
        console.log(" favorites   " ,favorites);

         return (
            <ul className="favorites">
              {
                favorites.map(cat=>(
                  <CatItem img={cat} key={cat} />
                ))
              }
          </ul>
         )
      }
      
      const App=()=>{
        const CAT1 = "https://cataas.com/cat/HSENVDU4ZMqy7KQ0/says/react";
        const CAT2 = "https://cataas.com/cat/BxqL2EjFmtxDkAm2/says/inflearn";
        const CAT3 = "https://cataas.com/cat/18MD6byVC1yKGpXp/says/JavaScript";

        const cats= [CAT1,CAT2];

        const [favorites , setFavorites]=React.useState(cats);
        const [counter ,setCounter ] =React.useState(1);
        const [mainCardImgSrc, setMainCardImgSrc] =React.useState(mainImageSrcArr[0]);

        function handleFormSubmit(e){
          e.preventDefault();
          setCounter(counter+1);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);

          setMainCardImgSrc(mainImageSrcArr[nIndex]);
          console.log("폼 전송 됨" ,counter , e.target.name.value);
        }

        function handleHeartClick(){
          console.log("하트 눌렀음");
          setFavorites([...favorites, CAT3])
        }

        return(
          <div>
            <Title>1번째 고양이 가라사대</Title>,
            <Form  handleFormSubmit={handleFormSubmit}  counter={counter} setCounter={setCounter} setMainCardImgSrc={setMainCardImgSrc} />
            <MainCard mainCardImgSrc={mainCardImgSrc}  handleHeartClick={handleHeartClick} alt="alt"   width="400" />
            <Favorites  favorites={favorites} />
          </div>

        )

      }
    
     

      const 여기다가그려=document.querySelector("#app");
       ReactDOM.createRoot(여기다가그려).render(<App />);
    </script>

  </body>
</html>

 

 

 

 

 

 

 

 

 

21.코드 정리하기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101812&category=questionDetail&tab=curriculum

 

 

~

      const MainCard=({mainCardImgSrc, onHeartClick ,alt,width })=>{
        return  (
          <div className="main-card">
          <img
            src={mainCardImgSrc}
            alt={alt}
            width={width}
          />
          <button onClick={onHeartClick}   >????</button>
        </div>
        );
      }



     const App=()=>{
        const CAT1 = "https://cataas.com/cat/HSENVDU4ZMqy7KQ0/says/react";
        const CAT2 = "https://cataas.com/cat/BxqL2EjFmtxDkAm2/says/inflearn";
        const CAT3 = "https://cataas.com/cat/18MD6byVC1yKGpXp/says/JavaScript";

        const cats= [CAT1,CAT2];

        const [favorites , setFavorites]=React.useState(cats);
        const [counter ,setCounter ] =React.useState(1);
        const [mainCardImgSrc, setMainCardImgSrc] =React.useState(mainImageSrcArr[0]);

        function handleFormSubmit(e){
          e.preventDefault();
          setCounter(counter+1);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);
          setMainCardImgSrc(mainImageSrcArr[nIndex]);
        }

        function onHeartClick(){
          setFavorites([...favorites, CAT3])
        }

        function updateMainCat(){
          setCounter(counter+1);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);
          setMainCardImgSrc(mainImageSrcArr[nIndex]);
        
        }


        return(
          <div>
            <Title>1번째 고양이 가라사대</Title>,
            <Form   updateMainCat={updateMainCat} />
            <MainCard mainCardImgSrc={mainCardImgSrc}  onHeartClick={onHeartClick} alt="alt"   width="400" />
            <Favorites  favorites={favorites} />
          </div>

        )

      }


 

 

 

 

 

 

 

 

 

 

 

 

22.로컬스토리지에 데이터 싱크하기

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101813&category=questionDetail&tab=curriculum

 

~

  const [counter ,setCounter ] =React.useState(Number(localStorage.getItem("counter")));


~

        function updateMainCat(){
          const nextCounter =counter+1;
          setCounter(nextCounter);
          localStorage.setItem("counter",nextCounter);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);
          setMainCardImgSrc(mainImageSrcArr[nIndex]);
        
        }

~

 

 

 

 

 

 

 

 

 

 

 

 

 

 

23.로컬스토리지에 데이터 싱크하기 2

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101814&category=questionDetail&tab=curriculum

 

 

 

~
    const jsonLocalStorage = {
      setItem: (key, value) => {
        localStorage.setItem(key, JSON.stringify(value));
      },
      getItem: (key) => {
        return JSON.parse(localStorage.getItem(key));
      },
    };


~




        function updateMainCat(){
          const nextCounter =counter+1;
          setCounter(nextCounter);
          jsonLocalStorage.setItem("counter",nextCounter);
          const nIndex= Math.floor(Math.random()* mainImageSrcArr.length);
          setMainCardImgSrc(mainImageSrcArr[nIndex]);
        }


 

 

 

 

 

 

 

 

 

 

 

 

 

[4. 지금까지 만든 앱 배포하기]

 

 

 

24.github pages 로 1차 배포

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=101816&category=questionDetail&tab=curriculum

 

 

 

???? Push가 안될 때 해결 방법

깃헙 아이디, 비밀번호 입력시
'Support for password authentication was removed on Au.. Please use a personal access token instead'
란 에러가 뜰 수 있습니다.

2021년 8월 13일부터 평문 비밀번호를 못 쓰도록 보안 업데이트가 되었기 때문인데요,
비밀번호 대신 Personal access token을 입력하면 됩니다.


아래 링크 참고하셔서 깃헙 사이트에서 토큰을 만드시고,  얘를 비밀번호처럼 이용하시면 됩니다.
메모장같은곳에 적어둬서 잊지 않도록 해주세요~

 

공식 문서: https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token
한글 블로그 글: https://jootc.com/p/201905122828

 

 

 

 

 

 

 

 

 

[5. 중간 정리]

 

25 지금까지 배운 개념 정리 1(JSX, 바벨, 컴포넌트,스타일링, 이벤트)

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=102198&category=questionDetail&tab=curriculum

 

 

 

 

 

 

 

26. 지금까지 배운 개면 정리 2(상태,리스트,폼,로컬스토리지)

 

강의:

 

https://www.inflearn.com/course/lecture?courseSlug=만들면서-배우는-리액트-기초&unitId=102199&category=questionDetail&tab=curriculum

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

about author

PHRASE

Level 60  라이트

아름다운 꿈을 지녀라. 그리하면 때묻은 오늘의 현실이 순화되고 정화될 수 있다. 먼 꿈을 바라보며 하루하루 그 마음에 끼는 때를 씻어나가는 것이 곧 생활이다. 아니, 그것이 생활을 헤치고 나가는 힘이다. 이것이야말로 나의 싸움이며 기쁨이다. - R.M. 릴케

댓글 ( 4)

댓글 남기기

작성