Session 9_1. React

김민재·2021년 8월 30일
0

TIL, WeCode, Course 

목록 보기
25/48
post-thumbnail

*🔐Study Keyword :

✅리액트 맛보기 냠냠

👨‍💻 React 기초 질문 5가지에 스스로 답해보기! 통과 시 Intro Session으로!

  1. 리액트란?
    ->사용자 인터페이스를 만들기 위한 자바스크립트 라이브러리이다.
  2. 리액트 특징은?
    -> 컴포넌트 기반, jsx를 사용, 선언적이라는 특징을 가지고 있다.
  3. 3세대 웹이 나타난 이유와 프론트 언어의 대표적인 라이브러리 3가지는?
    -> 웹의 복잡성의 증가로 인해 효율적인 개발과 처리를 위해서 등장하였으며
    대표적인 라이브러리로는 react, veu angular가 있다.
  4. 라이프 사이클 메소드의 순서는?
    ~~- constructor, render, componentDidMount, componentDidUpdate, componentwillUnMount 순이다. ~~
  • 순차적으로 1. 컴포넌트를 호출하면 mounting 중이며 2,. constructor에는 화면에 아직 아무것도 없는 상태 3. render 메소드를 호출하면 화면에 보이기 시작하고 4. componentDidMount에선 새로운 props, setState 메소드로 state를 변경하여 상태를 업데이트한 뒤 5. 컴포넌트가 더이상 필요하지 않는 unmounting 상태가 되면 componentWillUnmount가 된다.
  1. 컴포넌트란?
    재사용 가능한 독립적인 모듈(하나의 레고 블록 개념), UI의 단위를 의미한다.

-WHY USE❔❕

  • 리액트는 사용자 정의 태그를 만들어주는 기술 중 하나이며 리액트에선 이를
    component
    라고 부른다.

component 사용하면 좋은 점

- 1. '가독성'을 획기적으로 높인다.
- 2. component를 활용해 '재사용성'을 높인다.
- 3. 에러 발생시 component만 수정하면 되어 '유지보수에 효과적'이다.

-WHAT IS❓

1. JSX

  • JSX란 JavaScript 확장버전으로 syntax extension for JavaScript의 줄임말로 HTML문법을 JavaScript 코드 내부에 써준 것을 의미합니다.

1_1. JSX Element

  • JSX Element는 .js 파일 어디에서나 필요한 곳에 작성할 수 있고 변수에 저장또는 함수의 인자로도 넘길 수도 있다.

1_2. JSX attribute

  • 태그에 attribute를 주고 싶을 때는 항상 "" 쌍따옴표로 감싸주고 attribute를 추가하고 싶을 때는 실제 HTML에서 쓰는 attribute name(속성명)과 다를 수 있으니 항상 react 공식문서를 참고해서 써야한다.
    • EX> class 부여시 HTML에 쓰는 속성명은 class지만 JSX에서는 className이라고 추가해줘야한다는 사실!

1_3. self closing tag

  • JSX에서는 어떤 태그라도 self closing tag가 항상 가능하며 <input>과 같이 하나의 태그가 요소인 경우에는 <input />와 같이 항상 /으로 끝내줘야 한다.

1_4. Nested JSX

  • 1_4_1. (필수) 소괄호로 감싸기
    • 중첩된 요소를 만들려면 () 소괄호로 감싸야한다.
<script>
const good = (
// 중첩 요소 만들 시엔 무조건 소괄호로 감싸야한다.
<div>
    <p>hi</p>
</div>
);
</script>
  • 1_4_2. (필수) 항상 하나의 태그로 시작
    • 처음 요소가 sibling이면 안되고 무조건 하나의 태그로 감싸져야 한다
<script>
const introduce = (
// 무조건 하나의 태그로 감싸져야한다.
  <div>
    <li>hi</li>
    <li>my</li>
    <li>name is</li>
    <li>jamie</li>
  </div>
)
</script>

1_5. Rendering

  • 렌더링(rendering)란 html 요소, React 요소 등의 코드가 눈으로 볼 수 있도록 그려지는 것을 의미한다
  • eactDOM.render 함수를 사용하여 React 요소를 DOM node에 추가해 화면에 렌더한다.
  • 함수의 첫 번째 인자로 JSX로 React 요소를 넘기고, 두 번째 인자로 해당 요소를 렌더하고 싶은 container(부모요소)를 전달한다.
<script>
ReactDOM.render(
  <h1>Hello, world!</h1>, // 첫 번째 인자, JSX React 요소
  document.getElementById('root') // 두 번째 인자, 위의 요소를 렌더하고 싶은 부모 요소
);
</script>

2. Component와 Props

  • component(컴포넌트)란 재사용 가능한 UI 단위이다.
  • 동일 코드가 반복되는 부분을 하나의 component로 만든다면 다양한 곳에서 재사용할 수 있다.
    • 가령, component로 만든 부분의 디자인을 수정 할때도 css를 수정하면 해당 component가 쓰인 모든 곳에서 수정된 디자인이 모두 반영되는 장점을 가진다.
  • component를 사용하면 독립적으로, 재사용가능한 코드로 관리할 수 있다.
  • 하나의 component에 필요한 html, css, js(validation check)를 모두 합쳐서 만들 수도 있다.
  • component는 함수와 비슷한데, 함수 역시 함수의 기능이 독립적이며 재사용할 수 있기 때문이다.
  • component도 함수처럼 input을 받아서 return이 가능하다.
    React component에는 input을 props라 하고 화면에 보여져야 할 `React 요소가 return된다.

2_1. Component 만들기

  • React에서는 component를 class나 함수로 만들 수 있고 상황에 따라 무엇으로 만들지 달라지게 된다.

2_2. class로 Component 만들기

  • class로 component를 만드려면 React.Component를 extends해서(리액트의 컴포넌트로부터 클래스를 상속받아) 생성한다.
  • component 생성 시엔 render() 메서드를 필수적으로 정의하고, return도 필수이다.
  • render() 메서드를 무조건 정의해야한다는 의미는 component를 만들 때 필요한 메서드는 더 있지만 그 중에서 render() 메소드는 필수인 것을 뜻한다.
<script>
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
class Welcome() extends React.Component{
  render() {
    return <h1> hi, {this.props.name}</h1>;
  }
}
</script>

2_3. Component 사용

  • 정의한 컴포넌트는 함수나 class명으로 사용할 수 있고 작성 방식은 <함수, class명 /> 으로 작성한다.
  • 우리가 정의한 component를 사용할 땐 원하는 attribute를 추가할 수 있는데 component(함수)에서 parameter로 해당 attribute를 받아서 사용할 수 있는데 이를 props(property)라 부른다.
  • .(dot)으로 속성명에 접근 가능하고, props.속성명으로 속성 값을 가져올 수 있다.
<script>
// 1. Introduce 컴포넌트 정의
function Introduce (props) {
  return <h1>What's Up, My name is {props.name}</h1>
}
// 2. App 컴포넌트 정의
function App () {
  return(
  <div>
    <Introduce name = "kim" />
    <Introduce name = "minjae" />
    <Introduce name = "jamie(Eng)" />
   </div>   
  )
}
// 3. 화면에 React 요소 그려주기
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
</script>
  1. Introduce 컴포넌트: props.name의 값을 사용하고 있는 것을 보아 Introduce 컴포넌트를 사용한 측, 부모에서 name이라는 attribute를 부여
  2. App 컴포넌트는 div로 감싸져있고, <Introduce /> 컴포넌트를 세번 작성하며 각각의 name attribute를 부여
  3. ReactDOM.render 함수를 이용해 첫 번째 인자로 전달한 React 요소를 두 번째 인자로 전달한 root가 id인 요소 안에 집어넣어 <App /> 컴포넌트를 그려줌

2_4. 더 작은 Component로 분리하기

Comment 컴포넌트는 재사용 가능한 코드 단위인 컴포넌트 내에서 더 재사용할 수 있는 요소들을 의미합니다.

<script>
// 분리되기 전의 Comment 컴포넌트
function Commment(props) {
  return (
    <div className="comment">
      <div className="user-info">
        <img className= "avatar" 
          src = {props.author.avatarUrl}
          alt = {props.author.name}
        />
        <div className = "user-info-name">
          {props.author.name}
        </div>
      </div>
      <div className = "comment-text">
        {props.text}
      </div>
      <div className = "comment-date">
        {formatData(props.date)}
      </div>       
    </div>
  )
}
</script>

2_4_1. Comment 컴포넌트 쪼개기

  • .avatar 요소를 컴포넌트로 만들면 댓글창 이외에도 사용자 정보 등 여기저기 활용 가능하게 만들 수 있다.
  • Comment 컴포넌트에 있는 .avatar 부분을 떼와서 Avatar라는 이름으로 컴포넌트를 만들어주자
    • Comment 컴포넌트에선 props.author로 접근한 avatarUrl과 name을 Avatar 컴포넌트에서 직관적으로 사용하기 위해 user라는 이름으로 받아온다.
    • props.user에서 avatarUrl, name 값을 가져온다.
    • <Avatar />를 사용하는 부모 측에 user attribute를 추가한다.
function Commment(props) {
  return (
    <div className="comment">
      <div className="user-info">
        <Avatar user={props.author}/>
        <div className = "user-info-name">
          {props.author.name}
        </div>
      </div>
      <div className = "comment-text">
        {props.text}
      </div>
      <div className = "comment-date">
        {formatData(props.date)}
      </div>       
    </div>
  )
}
function Avatar(props) {
  return (
    <img className="avatar"
      src = {props.user.avatarUrl} 
      alt = {props.user.name}          
    />
  );
}
</script>
  • 재사용할 가능성이 있다면 모두 Component로 만들어주는 것이 효율적이다.
  • 마찬가지로 .user-info 부분을 컴포넌트로 만들기 위해 user-info 부분 역시 떼어다가 UserInfo라는 컴포넌트로 만든다.
// Commment 컴포넌트
function Commment(props) {
  return (
    <div className="comment">
      <UserInfo user = {props.author} />
      <div className = "comment-text">
        {props.text}
      </div>
      <div className = "comment-date">
        {formatData(props.date)}
      </div>       
    </div>
  )
}
// 분리한 Avatar 컴포넌트
function Avatar(props) {
  return (
    <img className="avatar"
      src = {props.user.avartarUrl} 
      alt = {props.user.name}          
    />
  );
}
// 분리한 UserInfo 컴포넌트
function UserInfo(props) {
  return (
    <div className="user-info">
      <Avartar user={props.user}/>
      <div className = "user-info-name">
        {props.user.name}
       </div>     
    </div>
  )
}
</script>
  • 최종 분리한 모습
<script>
// 1. 분리한 formatDate 컴포넌트
function formatDate(date) {
  return date;
}
// 2. 분리한 Avatar 컴포넌트
function Avatar(props) {
  return (
    <img className="avatar"
      src = {props.user.avatarUrl} 
      alt = {props.user.name}          
    />
  );
}
// 3. 분리한 UserInfo 컴포넌트
function UserInfo(props) {
  return (
    <div className="user-info">
      <Avatar user={props.user} />
      <div className="user-info-name">
        {props.user.name}
      </div>
    </div>
  );
}
// 기존의 comment 컴포넌트
function Comment(props) {
  return (
    <div className="comment">
      <UserInfo user={props.author} />
      <div className="comment-text">
        {props.text}
      </div>
      <div className="comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}
function App() {
  return (
    <Comment 
      author="jamie"
      text="컴포넌트 분리 성공"
      date="2021-08-29"
    />
  )
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
</script>

3. Component의 State

  • state란 컴포넌트의 상태 값으로 stateprops는 둘 다 object이고, 화면에 보여줄 정보(상태)를 가지고 있다는 점에서는 비슷한 역할을 한다.
  • props는 컴포넌트를 사용하는 부모 쪽에서 전달해야만 사용할 수 있고(parameter처럼)
  • state는 컴포넌트 내에서 정의하고 사용한다.
  • 여러 개의 state를 추가할 수도 state의 이름은 원하는대로 지을 수도 있다.
<script>
class Button extends React.Component {
  constructor() {
    super();
    this.state = {
      clicked: false
    }
  }
  render() {
    return (
      <div
        className="btn"
        onClick={()=>{this.setState({ clicked: !this.state.clicked })}}

        {this.state.clicked ? '좋아요' : '싫어요'}
      </div>
    );
  }
}
ReactDOM.render(
  <Button />,
  document.getElementById('root')
);
</script>

3_1. render 함수

<script>
render() {
  return (
    <div
      className="btn"
      onClick={()=>{this.setState({ clicked: !this.state.clicked })}}

      {this.state.clicked ? '좋아요' : '싫어요'}
    </div>
  );
}
</script>
  • <div /> 클릭시 onClick에 넘긴 함수 render함수가 실행하여 화면에 그려준다.

3_2. onClick={()=>{this.setState({ clicked: !this.state.clicked })}}

  • click 이벤트가 발생하면 clicked라는 state를 수정한다. this.setState() 함수를 통해 state를 업데이트 할 수 있다.
  • !this.state.clicked로 업데이트 한다는 것의 의미는 현재 clicked의 반대의(true => false, false => true)값을 저장함을 의미한다.
  • 따라서 onClick이 달려있는 <div />를 클릭할 때마다(이벤트 발생), clicked 상태가 true 혹은 false로 업데이트 된다.

3_3. {this.state.clicked ? ‘좋아요’ : ‘싫어요’}

  • clicked state가 true면 ‘좋아요’를 false면 ‘싫어요’를 보여준다.

3_4. constructor()

  • constructor()의 constructor는 class의 instance가 생성될 때 항상 호출되는 함수(생성자)다.
<script>
  constructor() {
    super();
    this.state = {
      clicked: false
    }
  }
</script>
  • 초기화 할 값들을 constructor에서 세팅해준다.
  • Button 컴포넌트를 그리기 위해선 this.state.clicked 값이 필요하다.
  • 제일 최초에는 값이 없으므로 constructor(생성자 함수)에서 값을 지정해줘야고 super() 키워드를 사용해 React.Component class에 있는 메서드들(ex> render)을 사용할 수 있다.

3_5. props와 state

  • <Button />에 type을 추가하여 Button 컴포넌트에서 props로 받을 수 있다.
  • class .like-btn는 배경색을 넣은 css로 this.props.type이 ‘like’면 .like-btn class 속성을 추가한다.
<script>
class Button extends React.Component {
  constructor() {
    super();
    this.state = {
      clicked: false
    }
  }
  render() {
    return (
      <div
        className={`btn ${this.props.type === 'like' ? 'like-btn' : ''}`}
        onClick={()=>{this.setState({ clicked: !this.state.clicked })}}

        {this.state.clicked ? '좋아요' : '싫어요'}
      </div>
    );
  }
}
ReactDOM.render(
  <Button type="like" />,
  document.getElementById('root')
);
</script>

4. React 컴포넌트의 Lifecycle

  • render, componentDidMount, componentDidUpdate, componentWillUnmount 등의 함수는 React.Component class에서 제공하는 메서드이다.
  • class로 컴포넌트를 생성하면 위의 메서드를 사용할 수 있고, 컴포넌트의 lifecycle에 따라 각자의 메서드가 호출된다.
  • 이는 컴포넌트를 만들어 lifecycle에 따라 state를 관리할 수 있도록 코드 수정이 가능하다는 의미이다.
  • 1초에 한 번 tictoc함수를 호출해서 현재시간을 초 단위로 업데이트해보자.
<script>
function tictoc(){
  const element = (
    <div>
      <h1> what's up world! </h1>
      <h2> It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root') 
  );
}
setInterval(tictoc, 1000);
</script>

4_1. 컴포넌트 만들기

  • 원래 코드에서 tictoc은 일반적인 함수이며, 내부에서 ReactDom.render를 호출해서 React 요소를 그려주고 있다.
  • 화면으로 보여질 부분을 함수를 사용해서 컴포넌트로 만들어보자.
<script>
// 화면으로 보여지는 Clock 컴포넌트
function Clock(props){
  return (
    <div>
      <h1> what's up world! </h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
   );
 }
 function tictoc() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}  
setInterval(tictoc, 1000);
</script>
  • 함수로 컴포넌트를 만들었지만 최종적으로는 Clock 컴포넌트를 호출하고 싶고, 매초 업데이트될 기능이 Clock 컴포넌트 내에서 이루어지기 위해서 class로 컴포넌트를 만들어보자.
  • 클래스로 컴포넌트를 만들 땐 -render() 함수가 꼭 필요하며 화면에 그려줄 부분을 return한다.
  • Clock 함수에 있던 return을 그대로 render()의 return으로 가져오고
    props.date를 this.props.date로 바꿔준다.
  • render 메서드는 초가 바뀔 때마다, 즉 1초마다 호출되어 내용을 변경해줘야 한다.
    그런데 Clock 컴포넌트(즉, Clock 인스턴스)는 mounting 동안 본인 컴포넌트 내에서 값이 업데이트 되어야 하므로, state로 변경 값을 관리해야하는데 이때 lifecycle 메서드가 필요하다.
<script>
class Clock extends React.Component {
   render() {
    return (
      <div>
        <h1> what's up world! </h1>
        <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
   );
  }
}
</script>

4_2. state 추가

  • Clock 컴포넌트에서 바뀌어야 하는 상태는 현재 시간으로 매 초마다 현재 시간을 새로 가져와야 한다. props로 받던 date 정보를 state로 바꿔서 관리해보자
<script>
class Clock extends react.component {
   render() {
return (
    <div>
      <h1> what's up world! </h1>
      <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
  ) ;
 }
}
</script>
  • this.state로 바꾸어줬으니 초기 세팅이 필요하다. constructor에서 세팅을 해야한다.
  • 원래 tictoc 함수 외부에 있던 setInterval를 Clock 컴포넌트 내부로 들여와서, 매 초마다 새로운 시간을 가져오도록 수정한다.

4_3. lifecycle 메서드 추가하기

  • 프로그래밍을 할 때, 사용하던 리소스가 더 이상 필요없어지면 없애주는 과정이 항상 필요하고 Clock 컴포넌트가 mounting 될 때는 timer를 추가하고, 더 이상 화면에서 나올 필요 없는 unmounting이 되는 순간 timer를 삭제해주면 된다.

이와 관련된 메서드는 componentDidMount, componentWillUnmount이다

<script>
class Clock extends React.Component {
  constructor (){
    super();   
    this.state = {
      date : new Date()
    };
  }
  componentDidMount(){
  }
  componentWillUnmount(){
  }
   render() {
return (
    <div>
      <h1> what's up world! </h1>
      <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
  );
 }
}
</script>
  • Clock 컴포넌트가 화면에 그려지자마자(render되자마자) componentDidMount 메서드가 호출되면서 timer를 시작한다.
  • 1초마다 tictock 함수를 호출하게 되고 tictoc 함수는 새로운 날짜를 가져와서 this.state.date에 업데이트 해준다.
  • 생성한 timer는 this.timerId에 저장한하는데 setInterval 함수를 호출해서 timer를 생성하면, 해당 timer의 id를 리턴하기에 이를 변수에 저장할 수 있다.
<script>
  componentDidMount(){
    this.timerID = setInterval(
     () => this.tictoc{},
      1000
    )
  }
</script>
  • unmount 될 때, componentWillUnmount 메서드가 호출되고 clearInterval 함수를 사용해 아까 만들었던 timer를 삭제해줍니다.
<script>
  componentWillUnmount(){
    clearInterval(this.timerID)
  }
</script>
  • 그리고 tictoc 함수를 구현한다.
<script>
class Clock extends React.Component {
  constructor () {
    super();
    this.state = {
      date : new Date()
    };
  }
  componentDidMount() {
    this.timerID = setInterval(
     () => this.tictoc(),
     1000
    );
  }
  componentWillUnmount(){
    clearInterval(this.timerID)
  }
  tictoc() {
    this.setState({
      date : new Date()
    });
  }
   render() {
      return (
        <div>
          <h1> what's up world! </h1>
          <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
        </div>
    );
  }
}
 ReactDOM.render(
    <Clock />,
    document.getElementById('root')
 );
</script>

전체적인 과정 (Clock 컴포넌트 Mount -> Unmount 과정)

  1. ReactDOM.render() 에서 첫 인자로 를 넘길 때, React는 Clock 컴포넌트의 constructor를 호출한다.
  2. Clock에서 초기 시간이 필요하므로 this.state에 현재 시간으로 초기화한다. 이후 Clock 컴포넌트의 render() 메서드가 호출된다.
  3. DOM에 render()의 return된 요소가 추가되면 componentDidMount 함수가 호출된다.
  4. Clock 컴포넌트의 tictoc 메서드가 매 초 호출될 수 있도록 timer를 추가한다.
  5. 매 초 브라우저가 tictoc 메서드를 호출하면서 this.state.date 값이 변한다.
  6. state가 변경되면 원래 componentDidUpdate 함수가 호출되지만, 우리는 정의하지 않았으므로 render()가 다시 호출되면서 바뀐 부분이 변경된다.
  7. DOM에서 Clock 컴포넌트가 삭제될 땐, componentWillUnmount가 호출되고 timer도 같이 멈추게 됩니다.

5. 이벤트 핸들링

  • React에서는 React element에 onClick 이벤트를 추가해서 event handler 함수를 넘겨준다.
  • 사실 React 라이브러리를 사용하지 않아도 같은 방식으로 event handler를 추가하는 방법도 있다.
<script>
class Button extends React.Component {
  constructor() {
    super();
    this.state = {
      clicked: false
    }
  }
  render() {
    return (
      <div
        className="btn"
        onClick={()=>{this.setState({ clicked: !this.state.clicked })}}

        {this.state.clicked ? '좋아요' : '싫어요'}
      </div>
    );
  }
}
ReactDOM.render(
  <Button />,
  document.getElementById('root')
);
</script>
  • Button 컴포넌트의 <div> 태그에 onClick 이벤트에 ()=>{this.setState({ clicked: !this.state.clicked })}인 event handler를 넘겨주었다.
  • 해당 요소를 click 할 때마다 event handler 함수가 실행되어, 클릭할 때마다 this.state.clicked 반대 boolean 값으로 clicked state를 업데이트 한다.
  • render 메서드 내부의 React 요소에 함수가 포함되면 코드가 깔끔해 보이지 않으니 event handler 부분을 분리해서 함수를 밖으로 뺄 수 있다.
  • Button 컴포넌트에 handleClick 메서드를 추가하고, onClick 이벤트에 this.handleClick이라는 event handler 함수를 넘겨준다.
<script>
class Button extends React.Component {
  constructor() {
    super();  
    this.state = {
      click: false 
    }
    this.handleClick =  
    this.handleClick.bind(this)
  } 
  handleClick() {
    this.setState({
      clicked: !this.state.clicked
    });
  }
  render() {
    return (
      <div
        className={`btn ${this.props.type === 'like' ? 'like-btn' : ''}`}
        onClick={this.handleClick}
 
        {this.state.click ? '좋아요' : '싫어요'}
      </div>
    );
  }
}
ReactDOM.render(
  <Button />,
  document.getElementById('root')
);
</script>
  • constructor에 추가된 코드를 보면
    this.handleClick = this.handleClick.bind(this);
    this.handleClick가 this.handleClick.bind(this)라는 뜻으로 constructor에서 this.handleClick에 이미 this를 bind시킨 handleClick 메서드를 대입해준다.
    -handleClick 메서드 내에서 this 키워드를 사용하고 있는데, 이 this는 Button 컴포넌트의 context여야 하기때문에 handleClick.bind(this)라고 작성해준 위치에서 그 this를 handleClick에 넘겨서 handleClick 메서드 내에서도 같은 this를 사용해야한다.
  • 그렇게하면 Button 컴포넌트의 this.state에 접근할 수 있고 setState 함수도 사용 가능하다.
  • 이벤트에 event handler 함수를 넘길때 bind를 해주지 않으면 event handler 함수내에서 this의 context를 잃어버려서 this가 undefined가 되니 React 요소에 이벤트를 추가할 경우 bind(this)를 해줘야한다.

*💡conclusion

  • 리액트의 기본 개념부터 잘 익히자

#📑Study Source

위코드 주말 사전 학습가이드에서:{}

profile
자기 신뢰의 힘을 믿고 실천하는 개발자가 되고자합니다.

0개의 댓글