클래스란?
객체 지향 프로그래밍에서 클래스는 특정 객체를생성하기 위해 변수와 함수를 정의하는 일종의 틀을 말한다. 객체를 정의하기 위한 상태와 함수로 구성되어 있다. 객체 단위로 코드를 그룹화하고 쉽게 재사용하려고 사용한다.

class Cat {
	// 생성자 함수
  constructor(name) {
		// 여기서 this는 이 클래스입니다.
		this.name = name; 
	}

	// 함수
	showName(){
		console.log(this.name);
	}
}

let cat = new Cat('perl');
cat.showName();
console.log(cat);

클래스를 상속한다는 건, 이미 만들어 둔 어떤 클래스를 가지고 자식 클래스를 만든다는 것이다.

class Cat {
	// 생성자 함수
  constructor(name) {
		// 여기서 this는 이 클래스입니다.
		this.name = name; 
	}

	// 함수
	showName(){
		return this.name;
	}
}

// extends는 Cat 클래스를 상속 받아 온단 뜻입니다.
class MyCat extends Cat {
	// 생성자 함수
  constructor(name, age) {
		// super를 메서드로 사용하기
		super(name); 
		this.age = age; 
	}
	
	// 부모 클래스가 가진 것과 같은 이름의 함수를 만들 수 있습니다.
	// 오버라이딩한다고 해요.
	showName(){
		// super를 키워드로 사용하기
		return '내 고양이 이름은 '+super.showName()+'입니다.';
	}
	
	showAge(){
		console.log('내 고양이는 '+this.age+'살 입니다!');
	}
}

let my_cat = new MyCat('perl', 4);
my_cat.showName();
my_cat.showAge();

super 키워드

  • 메소드로 사용할 수 있다.(constructor 안에서)
    • 부모의 constructor를 호출하면서 인수를 전달한다.
    • this를 쓸 수 있게 해준다.
  • 키워드로 사용할 수 있다.
    • 부모 클래스에 대한 필드나 함수를 참조할 수 있다.

 

let, const와 Scope

스코프(Scope)가 뭘까? 우리가 어떤 변수를 선언했을 때, 그 변수를 사용할 수 있는 유효범위를 스코프라고 부른다. 변수에 접근할 수 있는 범위다.

var: 함수 단위
let : block 단위 (변수 : let으로 선언한 변수는 값이 변할 수 있다.)
const : block 단위(상수: 한번 선언한 값은 바꿀 수 없다.)

function scope(){
	const a = 0;
	let b = 0;
	var c = 0;

	// {} 증괄호 안에 든 내용을 블럭이라고 표현해요.
	
	if(a === 0){
		const a = 1;
		let b = 1;
		var c = 1;
		console.log(a, b, c);
	}
	// 앗! c는 값이 변했죠? 
	// 그렇습니다. var는 함수 단위라서 if문 밖에서 선언한 값이 변했어요.
	// let과 const로 선언한 겂은 어떤가요? if문 안쪽 내용이 바깥 내용에 영향을 끼치지 않죠?
	console.log(a, b, c);
}


Spread 연산자 (Spread 문법)
어떤 객체 안에 있는 요소들을 객체 바깥으로 꺼내주는 연산자다.

let array = [1,2,3,4,5];
// ... <- 이 점 3개를 스프레드 문법이라고 불러요.
// 배열 안에 있는 항목들(요소들)을 전부 꺼내준다는 뜻입니다.
// 즉 [...array]은 array에 있는 항목을 전부 꺼내 
// 새로운 배열([] => 이 껍데기가 새로운 배열을 뜻하죠!)에 넣어주겠단 말입니다!
let new_array = [...array];

console.log(new_array);
//[ 1, 2, 3, 4, 5 ]

삼항 연산자는 if문의 단축 형태입니다. 사용법: 조건 ? 참일 경우 : 거짓일 경우

let info = {name: "mean0", id: 0};

let is_me = info.name === "mean0"? true : false;

console.log(is_me);
//true

Array 내장 함수
map
배열에 속한 항목을 변환할 때 많이 사용한다.
어떤 배열에 속한 항목을 원하는 대로 변환하고, 변환한 값을 새로운 배열로 만들어 준다.
원본 배열은 값이 변하지 않는다.
간단히 설명하면 원 배열의 값을 하나하나 불러와서 array_item에 담아서 오른쪽 함수 안의 구문을 반복한다는 것이다. for of와 조금 비슷하다.

const array_num = [0, 1, 2, 3, 4, 5];

const new_array = array_num.map((array_item) =>{ 
	return array_item + 1;
});
// 새 배열의 값은 원본 배열 원소에 +1 한 값입니다.
console.log(new_array);
// 원본 배열은 그대로 있죠!
console.log(array_num);
//[ 1, 2, 3, 4, 5, 6 ]
//[ 0, 1, 2, 3, 4, 5 ]

 

filter
어떤 조건을 만족하는 항목들만 골라서 새 배열로 만들어주는 함수이다.
원본 배열은 변하지 않고, 원하는 배열을 하나 더 만들 수 있다.

const array_num = [0, 1, 2, 3, 4, 5];

// forEach(콜백함수)
const new_array = array_num.filter((array_item) => {
	// 특정 조건을 만족할 때만 return 하면 됩니다!
	// return에는 true 혹은 false가 들어가야 해요.
	return array_item > 3;
});

console.log(new_array);
//[ 4, 5 ]

concat
배여로가 배열을 합치거나 배열에 특정 값을 추가해주는 함수이다.
원본 배열은 변하지 않는다.

const array_num01 = [0, 1, 2, 3];
const array_num02 = [3, 4, 5];

const merge = array_num01.concat(array_num02);

// 중복 항목(숫자 3)이 제거되었나요? 아니면 그대로 있나요? :)
console.log(merge);
//[0, 1, 2, 3, 3, 4, 5]

concat은 중복 항목을 제거해주지 않는다.
다른 내장함수와 함께 사용해서 제거해야 한다.
중복 제거를 원할 시에는 Set()을 사용하면 된다.

const array_num01 = [0, 1, 2, 3];
const array_num02 = [3, 4, 5];
// Set은 자바스크립트의 자료형 중 하나로, 
// 중복되지 않는 값을 가지는 리스트입니다. :)!
// ... <- 이 점 3개는 스프레드 문법이라고 불러요.
// 배열 안에 있는 항목들(요소들)을 전부 꺼내준다는 뜻입니다.
// 즉 [...array_num01]은 array_num01에 있는 항목을 전부 꺼내 
// 새로운 배열([] 이 껍데기가 새로운 배열을 뜻하죠!)에 넣어주겠단 말입니다!
const merge = [...new Set([...array_num01, ...array_num02])];

// 중복 항목(숫자 3)이 제거되었나요? 아니면 그대로 있나요? :)
console.log(merge);

from

쓰임새가 다양하다.

배열로 만들고자 하는 것이나 유사배열을 복사해서 새로운 배열로 만들 때 사용한다.

새로운 배열을 만들 때 사용한다. (초기화한다고도 표현한다.)

유사배열이란?
[a,b,c,b] 이 모양으로 생겼지만 배열의 내장 함수를 사용하지 못하는 것들이다. DOM nodelist같은 게 유사배열이다.

// 배열화 하자!
const my_name = "mean0";
const my_name_array = Array.from(my_name);
console.log(my_name_array);

// 길이가 문자열과 같고, 0부터 4까지 숫자를 요소로 갖는 배열을 만들어볼거예요. 
const text_array = Array.from('hello', (item, idx) => {return idx});

console.log(text_array);


// 새 배열을 만들어 보자!(=> 빈 배열을 초기화한다고도 해요.)
// 길이가 4고, 0부터 3까지 숫자를 요소로 갖는 배열을 만들어볼거예요. 
const new_array = Array.from({length: 4}, (item, idx)=>{ return idx;});

console.log(new_array);
//[ 'm', 'e', 'a', 'n', '0' ]
//[ 0, 1, 2, 3, 4 ]
//[ 0, 1, 2, 3 ]

 

quiz.
고양이들만 새 배열에 넣기.

const animals = ["복슬 강아지", "검정 고양이", "노란 햄스터", "강아지", "노랑 고양이", "고양이", "흰 토끼"];

let cats = [];
for (let i = 0; i < animals.length; i++) {
	let animal = animals[i];
	// indexOf는 파라미터로 넘겨준 텍스트가 몇 번째 위치에 있는 지 알려주는 친구입니다.
	// 파라미터로 넘겨준 텍스트가 없으면 -1을 반환해요!
	// 즉 아래 구문은 고양이라는 단어를 포함하고 있니? 라고 묻는 구문이죠!
	if (animal.indexOf("고양이") !== -1) {
		cats.push(animal);
	}
}
console.log(cats);

filter로 변환하면 아래와 같다.

const animals = ["복슬 강아지", "검정 고양이", "노란 햄스터", "강아지", "노랑 고양이", "고양이", "흰 토끼"];

let cats = animals.filter((animal) => {
    return animal.indexOf("고양이") !== -1;
})

console.log(cats)

 

=======================================

리액트로 넘어가 보자.

nvm 을 이용해 원하는 버전 node를 설치한다.

nvm install [설치할 버전]
nvm install 14.17.6
nvm ls # nvm으로 설치한 노드 버전 리스트 확인 명령어
node -v # 노드 버전 확인 명렁어
nvm use 사용할 노드 버전

npm으로 yarn을 설치했다. -g는 글로벌이라는 의미이다.

npm install -g yarn

 

# 옵션 global은 전역에 이 패키지를 깔겠다는 뜻입니다.
yarn add global create-react-app

CRA란 웹사이트를 만들 때 필요한 것을 몽땅 때려넣은 만든 패키지이다. 레고의 해리포터 성 만들기 같은 거라고 생각하면 편하다.

첫 리액트 프로젝트(week-1)를 만든다.

# yarn create react-app [우리의 첫 리액트 프로젝트 이름]
# 우리가 설치한 create-react-app 패키지를 써서 프로젝트를 만들어요.
# 주의! 꼭 sparta_react 폴더 경로에서 입력해주세요!
yarn create react-app week-1

week-1을 만들었으니 이동해서 시동해보자

cd week-1 # week-1 폴더로 이동합니다.
yarn start

하하 귀여운 첫페이지가 완성되었다.

JSX

HTML을 품은 JS === JSX!

 

import './App.css';

function App() {
  const div_back_styles = {
          width: '100vw',
          maxWidth: '400px',
          margin: '30px auto',
          flexDirection: 'column',

  }
  const color_green = {color:"green"}
  const div_styles = {
          height : "70px",
          borderBottom : "2px solid black",
          padding: "20px",
          width: '100vw',
          maxWidth: '400px',
          margin: '30px auto',
          flexDirection: 'column'
  }
  
  return (
    <div className="App">
      <div style={div_back_styles}>
      <div style={div_styles}>
        <h1 style={color_green}>안녕하세요!</h1>
      </div>
      <p style={{textAlign:'left'}}>이름을 입력해주세요.</p>
      <input type="text"/>
      </div>
    </div>
  );
}

export default App;

간단한 퀴즈를 풀어보았다.

 

Component란?

위에서 리액트는 레고라고 말씀 드렸죠? 컴포넌트는 블록이다!

이 웹사이트를 HTML로 간단히 표현해보면 아래와 같다.

<!DOCTYPE html>
<html lang="en">
  <head>
  </head>
  <body>
    <header> 
        ...
    </header>
    <div class="container">
        <div id="image-banner">
            ...
        </div>
        <div id="contents-1">
            ...
        </div>
    </div>
    <footer>
        ...
    </footer>
  </body>
</html>

이 코드를 조각조각 내보면 아래와 같이 나눌 수 있을 거예요. 나눈 조각 하나하나를 컴포넌트라고 부른다.

  1. <header/>
  2. <container/>
    1. <imagebanner/>
    2. <contents1/>
  3. <footer/>

즉, 이 웹 사이트는, 크게 <header/>, <container/>, <footer/> 세 개의 컴포넌트가 있고, <container/> 컴포넌트는, <imagebanner/>, <contents1/> 컴포넌트로 이루어져 있는 거죠!

State

state는 Component가 가지고 있는 데이터입니다.

Props

props는 Component가 부모 Component로부터 받아온 데이터입니다.

 

Component

함수형 컴포넌트

// 리액트 패키지를 불러옵니다.
import React from 'react'; 

// 함수형 컴포넌트는 이렇게 쓸 수도 있고
// function Bucketlist(props){
//     return (
//         <div>버킷 리스트</div>
//     );
// }

// 이렇게 쓸 수도 있어요. =>가 들어간 함수를 화살표 함수라고 불러요.
// 저희는 앞으로 화살표 함수를 사용할거예요.
// 앗 () 안에 props! 부모 컴포넌트에게 받아온 데이터입니다.
// js 함수가 값을 받아오는 것과 똑같이 받아오네요.
const BucketList = (props) => {

    // 컴포넌트가 뿌려줄 ui 요소(리엑트 엘리먼트라고 불러요.)를 반환해줍니다.
    return (
        <div>
            버킷 리스트
        </div>
    );
}

// 우리가 만든 함수형 컴포넌트를 export 해줍니다.
// export 해주면 다른 컴포넌트에서 BucketList 컴포넌트를 불러다 쓸 수 있어요.
export default BucketList;

클래스형 컴포넌트

import React from 'react';
import logo from './logo.svg';
import './App.css';
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from './BucketList';

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {

  constructor(props){
    super(props);
    // App 컴포넌트의 state를 정의해줍니다.
    this.state = {
      list: ['영화관 가기', '매일 책읽기', '수영 배우기'],
    };
  }

  // 랜더 함수 안에 리액트 엘리먼트를 넣어줍니다!
  render() {
      return (
      <div className="App">
        <h1>내 버킷리스트</h1>
        {/* 컴포넌트를 넣어줍니다. */}
        <BucketList/>
      </div>
    );
  }
}

export default App;

 

1주차 과제도 해보았다.

import logo from './logo.svg';
import './App.css';
import React from "react";

import Start from "./Start";

class App extends React.Component{
  constructor(props){
    super(props);

    this.state = {
      name: "고양이"
    };
  }

  render () {
    return (
    <div className="App">
      { <Start name={this.state.name}/>   /*name으로 state의name값을 보낸다. */}
    </div>
    )
  }
}

export default App;
import React from "react";
import img from "./neko.jpg";

const Start = (props) => {
  // 컬러셋 참고: https://www.shutterstock.com/ko/blog/pastel-color-palettes-rococo-trend/
  return (
    <div
      style={{
        display: "flex",
        height: "100vh",
        width: "100vw",
        overflow: "hidden",
        padding: "16px",
        boxSizing: "border-box",
      }}
    >
      <div
        className="outter"
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          flexDirection: "column",
          height: "100vh",
          width: "100vw",
          overflow: "hidden",
          padding: "0px 10vw",
          boxSizing: "border-box",
          maxWidth: "400px",
        }}
      >
        <img src={img} style={{ width: "80%", margin: "16px" }} />
        <h1 style={{ fontSize: "1.5em", margin: "0px", lineHeight: "1.4" }}>
          나는{" "}
          <span
            style={{
              backgroundColor: "#fef5d4",
              padding: "5px 10px",
              borderRadius: "30px",
            }}
          >
            { props.name        /*여기서 props로 부모에게서 받은 리스트의 name값을 받아온다. */}
          </span>
          에 대해 얼마나 알고 있을까?
        </h1>
        <input
          type="text"
          style={{
            padding: "10px",
            margin: "24px 0px",
            border: "1px solid #dadafc",
            borderRadius: "30px",
            width: "100%",
            // backgroundColor: "#dadafc55",
          }}
          placeholder="내 이름"
        />
        <button
          style={{
            padding: "8px 24px",
            backgroundColor: "#dadafc",
            borderRadius: "30px",
            border: "#dadafc",
          }}
        >
          시작하기
        </button>
      </div>
    </div>
  );
};

export default Start;

 

 

2주차!

SCSS를 쓰기위해

yarn add node-sass@4.14.1 open-color sass-loader classnames

로 설치해준다.

yarn add styled-components

styled-components란?

컴포넌트 스타일링 기법 중, 제가 가장 좋아하는 방식입니다! 😎 왜 좋아하냐구요? 많은 이유가 있지만, 두 개만 꼽아볼게요.

  • class 이름 짓기에서 해방됩니다!
  • 컴포넌트에 스타일을 적기 때문에, 간단하고 직관적입니다!

CSS-in-JS 라이브러리 중 하나입니다! 컴포넌트에 스타일을 직접 입히는 방식이라고 편하게 생각하셔도 됩니다!

 

라이프 사이클이란?

  • 컴포넌트의 라이프 사이클(= 컴포넌트 생명주기)은 정말 중요한 개념입니다! 컴포넌트가 렌더링을 준비하는 순간부터, 페이지에서 사라질 때까지가 라이프 사이클이에요.
  • 아래 도표는 어떻게 라이프 사이클이 흘러가는 지 그린 도표입니다.
      • 컴포넌트는 생성되고 → 수정(업데이트)되고 → 사라집니다.
      • 생성은 처음으로 컴포넌트를 불러오는 단계입니다.
      • 수정(업데이트)는 사용자의 행동(클릭, 데이터 입력 등)으로 데이터가 바뀌거나, 부모 컴포넌트가 렌더링할 때 업데이트 됩니다. 아래의 경우죠!
        • props가 바뀔 때
        • state가 바뀔 때
        • 부모 컴포넌트가 업데이트 되었을 때(=리렌더링했을 때)
        • 또는, 강제로 업데이트 했을 경우! (forceUpdate()를 통해 강제로 컴포넌트를 업데이트할 수 있습니다.)
      • 제거는 페이지를 이동하거나, 사용자의 행동(삭제 버튼 클릭 등)으로 인해 컴포넌트가 화면에서 사라지는 단계입니다.

  • 컴포넌트는 생성되고 → 수정(업데이트)되고 → 사라집니다.
  • 생성은 처음으로 컴포넌트를 불러오는 단계입니다.
  • 수정(업데이트)는 사용자의 행동(클릭, 데이터 입력 등)으로 데이터가 바뀌거나, 부모 컴포넌트가 렌더링할 때 업데이트 됩니다. 아래의 경우죠!
    • props가 바뀔 때
    • state가 바뀔 때
    • 부모 컴포넌트가 업데이트 되었을 때(=리렌더링했을 때)
    • 또는, 강제로 업데이트 했을 경우! (forceUpdate()를 통해 강제로 컴포넌트를 업데이트할 수 있습니다.)
  • 제거는 페이지를 이동하거나, 사용자의 행동(삭제 버튼 클릭 등)으로 인해 컴포넌트가 화면에서 사라지는 단계입니다.

라이프 사이클 함수는 클래스형 컴포넌트에서만 사용할 수 있습니다. 라이프 사이클을 아는 건 중요한데 왜 우리는 클래스형 컴포넌트보다 함수형 컴포넌트를 많이 쓰냐구요? 리액트 공식 매뉴얼에서 함수형 컴포넌트를 더 권장하기 때문입니다! (리액트 16.8버전부터 등장한 React Hooks으로 라이프 사이클 함수를 대체할 수 있거든요.) 더 많은 라이프 사이클 함수는 공식 문서에서 확인할 수 있어요 😉

 

 

06. Ref! 리액트에서 돔요소를 가져오려면?

React.createRef()

  • 이제 가상돔이 뭔지, 돔이 뭔진 알겠죠? 라이프 사이클도 알구요. 그런데 만약에, 내가 어떤 인풋박스에서 텍스트를 가져오고 싶으면 어떻게 접근해야할까요? (render()가 끝나고 가져오면 될까요? 아니면 mount가 끝나고? 아니, 그 전에 가상돔에서 가져오나? 아니면 DOM에서? 😖) → 답은, 리액트 요소에서 가져온다!

 

  • React 요소를 가지고 오는 방법
import React from "react";
import logo from "./logo.svg";
// BucketList 컴포넌트를 import 해옵니다.
// import [컴포넌트 명] from [컴포넌트가 있는 파일경로];
import BucketList from "./BucketList";
import styled from "styled-components";

// 클래스형 컴포넌트는 이렇게 생겼습니다!
class App extends React.Component {
  constructor(props) {
    super(props);
    // App 컴포넌트의 state를 정의해줍니다.
    this.state = {
      list: ["영화관 가기", "매일 책읽기", "수영 배우기"],
    };
    // ref는 이렇게 선언합니다! 
    this.text = React.createRef();
  }

  componentDidMount(){
		// 콘솔에서 확인해보자!
    console.log(this.text);
		console.log(this.text.current);
  }

  // 랜더 함수 안에 리액트 엘리먼트를 넣어줍니다!
  render() {
    
    return (
      <div className="App">
        <Container>
          <Title>내 버킷리스트</Title>
          <Line />
          {/* 컴포넌트를 넣어줍니다. */}
          {/* <컴포넌트 명 [props 명]={넘겨줄 것(리스트, 문자열, 숫자, ...)}/> */}
          <BucketList list={this.state.list} />
        </Container>

        <div>
          <input type="text" ref={this.text}/>
        </div>
      </div>
    );
  }
}

const Container = styled.div`
  max-width: 350px;
  min-height: 80vh;
  background-color: #fff;
  padding: 16px;
  margin: 20px auto;
  border-radius: 5px;
  border: 1px solid #ddd;
`;

const Title = styled.h1`
  color: slateblue;
  text-align: center;
`;

const Line = styled.hr`
  margin: 16px 0px;
  border: 1px dotted #ddd;
`;

export default App;

 

********************************************************

새 프로젝트를 시작할 때 해야할 순서!!!!!

+ 예시로 네모 카운트 숫자에 따라 계속 찍어 내는 프로젝트

(1) 새 CRA 만들기

yarn create react-app nemo

(2) index.js에서 <React.StrictMode> 부분을 지우기

(3) App.js를 class형 컴포넌트로 바꾸고 시작!

// App component를 class형으로!
import React from 'react';

class App extends React.Component {

  constructor(props){
    super(props);

    this.state = {}
  }

  componentDidMount(){

  }
  
  render(){

    return (
      <div className="App">
        
      </div>
    );
  }
}

export default App;

state에 count라는 변수를 추가하고, count 숫자만큼 네모칸을 화면에 띄우기

import React from "react";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      count: 3, // 숫자넣기!
    };
  }

  componentDidMount() {}

  render() {
    // 배열을 만듭니다.
    // Array.from()은 배열을 만들고 초기화까지 해주는 내장 함수입니다.
    // Array.from()의 첫번째 파라미터로 {length: 원하는 길이} 객체를,
    // 두번째 파라미터로 원하는 값을 반환하는 콜백함수를 넘겨주면 끝!
    // array의 내장함수 대부분은 콜백 함수에서 (현재값, index넘버)를 인자로 씁니다.
    const nemo_count = Array.from({ length: this.state.count }, (v, i) => i);

    // 콘솔로 만들어진 배열을 확인해봅니다. 숫자가 0부터 순서대로 잘 들어갔나요?
    console.log(nemo_count);

    return (
      <div className="App">
        {nemo_count.map((num, idx) => {
          return (
            <div key={idx}
              style={{
                width: "150px",
                height: "150px",
                backgroundColor: "#ddd",
                margin: "10px",
              }}
            >
              nemo
            </div>
          );
        })}
      </div>
    );
  }
}

export default App;

더하기, 빼기 버튼을 만들고,

return (
      <div className="App">
        {nemo_count.map((num, idx) => {
          return (
            <div key={idx}
              style={{
                width: "150px",
                height: "150px",
                backgroundColor: "#ddd",
                margin: "10px",
              }}
            >
              nemo
            </div>
          );
        })}

        <div>
          <button>하나 추가</button>
          <button>하나 빼기</button>
        </div>
      </div>
    );

함수를 만들어서

addNemo = () => {
    // this.setState로 count를 하나 더해줍니다!
    this.setState({ count: this.state.count + 1 });
  };

  removeNemo = () => {
    // 네모 갯수가 0보다 작을 순 없겠죠! if문으로 조건을 걸어줍시다.
    if (this.state.count > 0) {
      // this.setState로 count를 하나 빼줍니다!
      this.setState({ count: this.state.count - 1 });
    }else{
      window.alert('네모가 없어요!');
    }
  };

연결하자!

		<div>
          {/* 함수를 호출합니다. 이 클래스 안의 addNemo 함수를 불러오기 때문에 this.addNemo로 표기해요. */}
          <button onClick={this.addNemo}>하나 추가</button>
          <button onClick={this.removeNemo}>하나 빼기</button>
        </div>

 

오늘은 여기까지만... 너무 지친다...

+ Recent posts