[ 인프런 워밍업 클럽 Study FE 0기 ]  Week 2 발자국

[ 인프런 워밍업 클럽 Study FE 0기 ] Week 2 발자국

발자국

워밍업 스터디 클럽이 2주 차에 접어들었습니다. 해당 발자국은 따라 하며 배우는 리액트 A-Z (섹션 0-5) 중심으로 작성되었습니다.


요약

React를 사용하려면 Node.js가 필요하다. Node.js를 설치하면 NPM도 같이 설치되니 꼭 Node.js를 설치하자. Node.js 공식 홈페이지에 접속하면 2개의 Node 버전이 있는데, 그중에서 안정적인 버전인 LTS를 설치하면 된다.

 

Section 01. React

[ React란? ]

리액트는 사용자 인터페이스(user interface, UI)를 만들기 위해서 사용되는 자바스크립트의 라이브러리다. 리액트는 인터렉션이 많은 웹 앱을 개발하기 위해서 주로 사용된다. 이렇게 사용자 인터페이스를 만들기 위해 도움을 주는 TOOL로는 리액트 말고도 Vue.js와 Angular.js가 있다.

  • React: 라이브러리

  • Angular, Vue: 프레임워크

[ Framework vs Library ]

프레임워크와 라이브러리를 대략 설명하자면 다음과 같다.

  • Framework : 어떠한 앱을 만들기 위해 필요한 대부분의 것을 가지고 있다.

  • Library : 어떠한 특정 기능을 모듈화 해 놓은 것이다.

프레임워크는 앱을 만드는데 필요한 대부분의 라이브러리를 가지고 있으며, 라이브러리들은 특정 기능을 위해 모듈화 되어있다.

리액트는 라이브러리이다. 왜냐? 리액트는 전적으로 UI를 렌더링 하는 데 관여하기 때문이다. 리액트는 여러 모듈을 사용하며 앱을 관리한다.

  • 라우팅: react-router-dom …

  • 상태관리: redux, mobx …

  • 빌드: webpack, npm …

  • 테스팅: Eslint, Mocha …

[ React Component ]

리액트를 공부하다보면 무조건 마주치는 단어가 있다. 바로 컴포넌트이다. 리액트는 컴포넌트 기반이라고 하는데, 이 컴포넌트는 무엇을 말하는 것일까?

  • 컴포넌트(Component): React로 만들어진 웹/앱을 이루는 최소한의 단위

  • 리액트는 이 컴포넌트를 통해서 웹/앱을 개발하게 된다.

리액트는 여러 컴포넌트 조각으로 되어있다. 이것은 블록같다고 생각하면 된다. 여러 블록 조각을 맞추고 쌓아 올려 하나의 블록 작품을 완성하는 것. 리액트도 마찬가지로 컴포넌트를 이리저리 조합하고 쌓아올려 하나의 웹 페이지를 구성하게 된다.

리액트 컴포넌트에는 2가지가 있다.

  • 클래스형 컴포넌트

  • 함수형 컴포넌트

React는 여러 컴포넌트 조각으로 구성된다.

개인적인 설명을 덧 붙이자면 리액트는 레고 블럭과 같다고 생각한다. 레고 블럭들을 하나 둘 씩 쌓아 올려 하나의 완성된 레고 작품을 만드는 것이다.

[ Component 종류 ]

React는 2개의 컴포넌트 종류가 있다.

  • 클래스형 컴포넌트(Class Components)

class App extends Component {
  render() {
    return <h1>Hello, ReactJS!</h1>;
  }
}
  • 함수형 컴포넌트(Functioanl Components)

function App() {
  return <h1>Hello, ReactJS!</h1>;
}

💡 현재 함수형 컴포넌트를 HOOK이랑 해서 많이 사용한다.

💡 참고로 컴포넌트를 작성할 때 반드시 대문자 시작을 해야 한다. 소문자 시작 시 body, h1, p 같은 DOM 태그로 인식해 버린다.

[ 브라우저가 그려지는 원리와 가상 돔 ]

React의 가장 큰 특징은 가상 돔(Virtual DOM)이다. 이것을 사용하는 이유는 인터렉션 때문이다. 이 인터렉션에 의해 DOM에 변화가 발생하면 다시 DOM을 재구성하기 시작한다.

JS 발자국에도 남겼었지만 웹 브라우저의 경우 다음과 같은 과정을 겪고 이 과정은 비용이 꽤 든다.

  • Critical Render Path (웹 페이지 렌더링 과정)


    : 데이터 파싱(HTML) ➔ DOM Tree 생성 ➔ CSSOM Tree 생성 ➔ JS 실행 ➔ Render Tree 생성 ➔ Layout 생성 ➔ Paint

DOM을 재구성 한다는 것은 위 렌더링 과정을 다시 반복한다는 것이다. 즉, 인터렉션이 일어날때마다 위 과정을 다시 한다. 이것을 보완하기 위해서 나온 것이 가상 돔이다.

가상돔 과정을 살펴보자..!

  • 데이터가 바뀌면 가상 돔에 렌더링 되고, 이전에 생긴 가상 돔과 비교를 해서 바뀐 부분만 실제 돔에 적용 시킨다.

    • 바뀐 부분을 찾는 과정을 Diffing이라고 부른다.

    • 바뀐 부분만 실제 돔에 적용 시키는 것을 Reconciliation(재 조정)이라고 부른다.

[ Create React App 을 이용해서 리액트 설치하기 ]

create-react-app 을 통해서 원하는 위치에 리액트를 설치할 수 있다. 이 때, Webpack과 Babel이 함께 설치가 된다. 따라서 React 앱 생성 전에 Webpack과 Babel에 대해서 간단히 알고 가자.

  1. Webpack

  • 정의: Webpack: 웹팩은 오픈 소스 자바스크립트 모듈 번들러써 여러 개로 나누어져 있는 파일들을 하나의 자바스크립트 코드로 압축고 최적화하는 라이브러리이다.

  • 장점

    • 여러 파일의 자바스크립트 코드를 압축하여 최적화할 수 있기 때문에 로딩 줄일 수 있. (네트워크 비용 줄음)

    • 모듈 단위로 개발이 가능하여 가독성과 유지 보수가 쉽다.

  1. Babel

  • 정의: 최신 자바스크립트 문법을 지원하지 않는 브라우저들을 위해서 최신 자바스크립트 문법을 구형 브라우저에서도 돌 수 있도록 변환 시켜주는 라이브러리이다.

⇒ 이러한 Webpack과 Babel은 개발자가 React 개발 시 알아서 설정 해야 하지만 Create-React-App을 사용해서 React 앱을 생성하면 Babel이나 Webpack 설정이 이미 되어있기 때문에 준비 시간이 단축된다.

[ Create-React-App ]

  1. 프로젝트를 진행할 폴더 생성

  2. VSC에서 해당 프로젝트 폴더 열기

  3. Termial에 npx create-react-app 생성할파일명 입력

    • 강의에서는 npx create-react-app ./을 입력하였다.

    • ./는 현재 위치를 뜻한다.

[ npx create-react-app 에 대하여 ]

  • npx: 노드 패키지 실행을 도와주는 도구이다.

  • npx create-react-app이란 npm 레지스트리에 잇는 패키지를 ./에 실행시켜서 React를 설치해 주는 것이다.

실행하는 방법

  • 실행하고자 하는 리액트 파일 위치에서 npm run start 입력

(강의에서는 npm run start방법만 소개시켜 주셧는데 npm start도 가능하다.)

🤔 개인적으로 요즘 vite에 관한 이야기가 보이는데 이도 조사해 보아야겠다…

 

Section 2. 간단한 To-Do 앱 만들며 리액트 익히기

[ create react app ]

create-react-app으로 리액트를 설치하면 여러 파일이 등장하는데, 이 중에서 절대로 이름을 수정해서는 안되는 파일이 존재한다.

  1. public/index.html: 페이지 템플릿

  2. src/index.js: 자바스크립트 시작 점

조심하자!

💡그리고 우리가 새로 js, jsx, css 등 직접 생성할 파일들은 src 폴더에서 하면 된다. Webpack이 src/ 부분에만 작동하기 때문이라고 한다.

[ package.json ]

해당 프로젝트에 대한 정보들이 들어있다. 프로젝트 이름, 버전, 필요한 라이브러리와 라이브러리들의 버전이 명시되어 잇다.

[ 싱글 페이지 애플리케이션(single-page application, SPA) ]

싱글 페이지 애플리케이션(single-page application, SPA)은 서버로부터 완전한 새로운 페이지를 불러오지 않고 현재의 페이지를 동적으로 다시 작성함으로써 사용자와 소통하는 웹 애플리케이션이나 웹사이트를 말한다.

  • 위키백과

React.js는 SPA이다. 즉, 어떠한 데이터에 관한 교체 이벤트가 발생했을 때, 서버로부터 페이지를 새롭게(html 파) 받아와 구성하는 것이 아니라 content를 바꿔치기 한다. 이는 HTML 5의 History API를사용해서 가능하게 만든다.

[ History API ]

전통적인 웹 사이트는 a page에서 b page로 이동할 때 a.html을 보여주다가 b.html을 보여주면 되었지만 SPA의 경우 오직 1개의 HTML(index.html)이 존재한다. 따라서 페이징 전환을 하기 위해서 HTML 5 History API를 이용한다.

  • History.back(): 세션 기록의 바로 뒤 페이지로 이동하는 비동기 메서드

  • History.forward(): 세션 기록의 바로 앞 페이지로 이동하는 비동기 메서드

  • History.go(): 특정한 세션 기록으로 이동하게 해 주는 비동기 메서드

  • History.pushState(): 주어진 데이터를 세션 기록 스택에 넣어준다.

  • History.replaceState(): 최근 세션 기록 스택의 내용을 주어진 데이터로 교체한다.

생성했던 React 프로젝트에서 public/index.js를 살펴보면 <div id="rood"></div>가 있다.

그리고 src/index.js 코드에는 document.getElementById('root')라는 코드가 있다.

자바스크립트 파일의 시작 점인 src/index.js에서 id값이 rood인 요소를 찾아 그곳에 해당 요소들을 렌더링하는 것이다. 즉, div라는 최상위 루트 노드 아래에 직접 정의한 요소를 더해 화면을 꾸며나가는 것이다!!

[ JSX ]

JSX는 Javascript Syntax Extension의 약자로 자바스크립트의 확장 문법이다.

  • 리액트에서는 이 JSX를 이용해서 화면에서 UI가 보이는 모습을 나타내준다.

  • JSX 사용이 필수는 아니나 사용하면 가독성이 너무 좋아서 필수 아닌 필수이다. (애초에 리액트 개발자들 대부분이 JSX를 사용한다고 한다.)

  • JSX는 createElement를 쉽게 사용하기 위해서 사용한다.

    • 모든 UI를 만들때 마다 createElement를 사용해서 컴포넌트를 만들 수 없다.

Ract는 React.crateElemnt API를 사용해서 엘리먼트를 생성한 후에 이 엘리먼트를 In-Memory에 저장한다. 그리고 RaectDOM.render 함수를 통해 웹 브라우저에 그린다.

JSX를 사용하면 Babel이 사용한 문법을 crateElemnt로 자동 변환해준다. 따라서 그냥 개발자는 자유롭게 JSX 사용하면 된다.

단, JSX는 컴포넌트에 여러 요소가 있다면 반드시 부모 요소 하나로 감싸줘야 한다.

// 안된다.
// 자식 요소가 여러 개 라면 부모 요소로 감싸줘라.
function hello() {
  return (
    <div>Hello, Raect!</div>
    <div>Hello, Wrold!</div>
  );
}

// 이렇게 말이다.
function hello() {
  return <div>
      <div>Hello, Raect!</div>
      <div>Hello, Wrold!</div>
    </div>;
}

💡 만약 JSX에서 JS 코드를 사용하고 싶다면 { } 내부에 작성해주면 된다.

[ React와 Key ]

map()을 사용한다면 언제나 명심해야 하는 것. KEY. 이것을 넣지 않는다면 에러가 발생한다.

React에서 요소의 리스트를 나열할 때는 Key를 넣어줘야 한다. Key는 React가 변경, 추가 또는 제거된 항목을 식별하는 데 도움이 된다.

추가적으로, 이 Key에 지정하는 값은 순회하고자 하는 목록의 아이템에 대한 ID 값이면 된다. 즉, 고유한 값이여야 한다. 정 없으면 index 넣어도 되지만 index 값은 권장하지 않는다.

리액트는 가상 돔을 이용해서 바뀐 부분만 실제 돔에 적용한다. 그렇다면 리스트를 나열할 때 바뀐 부분만 어떻게 찾을까? 바로 이 key를 이용해서 어떠한 부분이 바뀌었는 인식하는 것이다.

[ state ]

정말 정말 중요한 개념!!!

리액트에서 데이터가 변할 때 화면을 다시 렌더링 해주기 위해서 React State를 사용한다. State란 무엇일까?

  • 간단히 말해서 변수이다.

  • 단, 이 변수의 값이 변경되면 컴포넌트들이 재렌더링 된다.

  • state에는 리액트의 흐름에 관한 데이터와 관련이 있다.

 

Section 3. To-Do 앱 최적화 하기

[ React HOOK ]

엄청나게 중요하다. 이 HOOK은 클래스형 컴포넌트처럼 함수형 컴포넌트에서도 state와 생명주기 메서드를 사용할 수 있도록 해주는 메서드이다.

클래스 형 컴포넌트에서는 Mounting, Updating, Unmounting 3단계 따라서 생명주기 메서드를 제공한다.

  1. Mounting: componentDidMount()

  2. Updating: componentDidUpdate

  3. Unmounting: componentWillUnmount()

함수형 컴포넌트에서는 이를 위해 HOOK을 사용한다.

[ HOC(Higher Order Component) ]

화면에서 재사용 가능한 로직만을 분리해서 component로 만들고, 재사용 불가능한 UI와 같은 다른 부분들은 parameter로 받아서 처리하는 방법이다.

  • HOC는 HOOK이 나오기 전에 사용했던 부분이다.

  • Wrapper가 많아지면 흐름 파악이 어려워서 이제 잘 안 쓴다.

  • HOC를 만들고 싶으면 Custom HOOK을 사용하자.

[ HOOK ]

기본적으로 알고 있어야 할 HOOK은 다음과 같다.

  • useState()

    • 리액트의 유동적인 데이터들은 state에 담아 사용하기 위해 이용하는 HOOK

    • 클래스형 컴포넌트의 setState와 같이 state 객체에 대한 업데이트 실행

    • 단!!! state 변경 시 재 렌더링이 일어남

  • useEffect()

    • 사이드 이팩트 처리 HOOK

    • 클래스형 컴포넌트의 생명 주기 함수 역할 수행

  • useMemo()

    • 최적화 용 HOOK, 의존성 배열에 따라 작동

    • 의존성 배열에 있는 값이 변하면 지정한 함수를 실행하여 해당 반환 값을 반환

  • useCallback()

    • 최적화 용 HOOK, 의존성 배열에 따라 작동

    • 의존성 배열에 있는 값이 변하면 함수를 반환

  • useRef()

    • 요소의 참조를 위해 사용하는 HOOK

[ Props ]

  • Props는 Properties의 줄임말

  • 상위 컴포넌트에서 하위 컴포넌트로 데이터를 전송하고 싶을 경우 사용

  • 읽기 전용으로 자녀 컴포넌트에서 강제로 이 값을 변경할 수 없다.

    • 전달 받은 props가 state고 이 값을 바꾸고 싶다면 props로 set함수를 넘기고 이것을 이

[ TailWindCSS ]

  • HTML 안에서 CSS 스타일을 할 수 있게 해주는 CSS 프레임워크

  • 빠른 스타일 작업 가능

  • id 혹은 class 명을 작성하기 위해 머리를 혹사 시키지 않아도 된다.

  • class에 특정 키워드를 넣어서 CSS 조작

    • 정해진 속성 키워드가 워낙 많으니 공식 홈페이지 검색 필수다

[ 리액트 불변성 ]

불변성을 지키며 개발을 하자!

  • 참조 타입에서 객체나 배열의 값이 변할 때 원본 데이터가 변경되면 예상치 못한 오류가 발생할 수 있다.

  • 불변성을 지킬 수 있는 참조 관련 메서드:

    • spread operator, map, filter, slice, reduce

  • 불변성을 해치는 참조 관련 메서드:

    • splice, push

[ React.memo ]

React.memo는 Higher-Order Components(HOC)이다. 불필요한 컴포넌트 렌더링을 방지할 수 있게해준다. (일종의 최적화 용 HOC)

 

Section 4-5. Netflix 앱 만들기

주로 실습 내용 이었다. 정리할 이론만 추려내 보겠다.

[ Styled Component ]

  • 자바스크립트 파일 안에서 CSS를 처리할 수 있게 해주는 라이브러리

[ React Router Dom ]

React Router DOM을 이용하면 웹/앱에서 동적 라우팅을 구현할 수 있다. 라우팅이 실행 중인 앱 외부의 구성에서 처리되는 기존 라우팅 아키텍처와 달리 React Router DOM은 앱 및 플랫폼의 요구 사항에 따라 컴포넌트 기반 라우팅을 용이하게 한다.

React Router DOM을 사용하기 위해서는 몇 가지 설정을 해야한다.

  1. index.js에서 BrowerRouter로 루트 컴폰너트를 감싸준다.

  • BrowserRouter은 HTML 5 History API를 사용하여 UI를 URL과 동기화 된 상태로 유지해준다.

import { BrowserRouter } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter >
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
  1. 여러 컴포넌트 생성 및 라우트를 정의한다.

  • Routes와 Route를 사용한다.

  • Routes: 앱에서 생성될 모든 개별 경로에 대한 컨테이너 상위 역할을 한다.

  • Route: 단일 경로를 만드는 데 사용된다.

    • path 속성: 원하는 컴포넌트의 URL 경로를 지정한다.

    • element 속성: 경로에 맞게 렌더링 되어야 하는 컴포넌트를 지정한다.

import { Routes, Route } from "react-router-dom";

function App() {
  return (
    <div className="app">
      <Routes>
        <Route path="/" element={<Home />}>
          <Route path="about" element={<About />} />
          <Route path="contact" element={<Contact />} />
        </Route>
      </Routes>
    </div>
  );
}

+) <Link />를 통해 경로 이동하기

  • Link 구성 요소는 HTML의 a 태그와 유사하다.

  • to 속성은 링크가 유저를 데려가는 경로를 지정한다.

  • 앱 구성 요소에 나열된 경로 이름을 생성했기 때문에 링크를 클릭하면 경로를 살펴보고 해당 경로 이름으로 구성 요소를 렌더링한다.

import { Link } from "react-router-dom";

function Home() {
  return (
    <div>
      <h1>홈페이지</h1>
      <Link to="about">About 페이지를 보여주기</Link>
      <Link to="contact">Contact 페이지를 보여주기</Link>
    </div>
  );
}

[ 중첩 라우팅 ]

  • 라우팅은 중첩 처리가 가능하다.

[ Outlet ]

  • 자식 경로 요소를 렌더링하려면 부모 경로 요소에서 Outlet를 사용해야한다.

  • 하위 경로가 렌더링될 때 중첩된 UI가 표시될 수 있다.

  • 부모 라우트가 정확히 일치하면 자식 인덱스 라우트를 덴더링하거나 인덱스 라우트가 없으면 아무것도 렌더링하지 않는다.

[ useNavigate ]

[ useParams ]

  • :style 문법을 path 경로에 사용했다면 useParams()로 읽을 수 있다.

function test() {
  return (
    <Routes>
      <Route path="invoices/:invoiceId" element={<Invoice />} />
    </Routes>
  );
}

function Invoice() {
  let params = useParams();
  return <h1>Invoice {params.invoiceId}</h1>;
}

[ useLocation ]

  • 현재 위치의 객체를 반환

  • 현재 위치가 변경될 때마다 일부 side effect를 수행하려는 경우 유용하다.

[ useRoutes ]

  • <Routes>와 기능적으로 동일하나 <Route> 요소 대신 자바스크립트 객체를 사용하여 경로를 정의한다.

  • 일반 <Route> 요소와 동일한 속성을 갖지만 JSX가 필요하지 않는다.

[ Custom HOOK ]

  • 개발자가 정의하는 HOOK이다.

  • HOOK의 이름은 use로 시작해야 한다.

  • 참고로 HOOK은 함수형 컴포넌트 또는 커스텀 HOOK에서만 호출이 가능하다.

  • 따라서 커스텀 HOOK도, 함수용 컴포넌트 또는 HOOK 내부에서 호출되어야 한다.

강의에서는 useDebounce과 useOnClickOutside HOOK을 만들었다.

  • useDebounce: input 요소에서 데이터 입력이 발생하면 설정한 set함수 때문에 매번 state 값이 바뀌고 재 렌더링이 일어난다. 따라서 keyup 이벤트의 처리를 지연시키는 커스텀 HOOK이다. (코드는 강의를 참고하자!)

  • useOnClickOutside HOOK: 모달 창 밖의 부분을 클릭하면 해당 모달 창이 꺼지는 기능을 수행하는 HOOK이다. (코드는 강의를 참고하자!)

이런 식으로 HOOK을 만들고 활용하는구나 싶었다…


미션

과제 총 합본 https://www.inflearn.com/blogs/7021

 

JS 미션 03. 퀴즈 앱

[ 구현 해야하는 기능 ]

1. 퀴즈 문제, 문제에 해당 하는 선택지 (선택지의 갯수가 매번 다름)

2. 답 선택 시, 정답 여부에 따라 배경의 색상이 변경되어야 함

  • 문제는 data.json을 직접 작성하여 동적 생성했습니다. JS 복습 겸으로 해당 주제로 퀴즈 앱을 간단하게 만들어 봤습니다. 미션을 진행하며 문제는 없었습니다.

 

JS 미션 04. 책 나열 앱

[ 구현 해야하는 기능 ]

1. 책 이름 입력 란

2. 책 저자 입력 란

3. 제출 버튼을 누르면 입력한 정보를 저장 함

3-1. 제출 시 제출 했다는 안내 문구 떠야 함

4. 제출된 데이터는 책 리스트에 출력 됨

  1. 아이템은 다음과 같은 기능을 가져야 함


    5-1. 표기 할 데이터: 책 이름, 저자
    5-

    2. 각 아이템에는 삭제 기능이 있어야 함

     

  • 구현하는데 문제가 없었습니다.

 

REACT 01. 예산 계산기

[ 구현 해야하는 기능 ]

1. 지출 항목 입력 란

2. 지출 비용 입력 란

3. 제출 버튼을 누르면 입력한 정보를 저장 함

3-1. 제출 시 제출 했다는 안내 문구 떠야 함

  1. 아이템은 다음과 같은 기능을 가져야 함


    5-1. 표기 할 데이터: 지출 항목, 지출 비용
    5-

    2. 각 아이템에는 수정 및 삭제 기능이 있어야 함

  2. 수정 버튼 클릭 시 수정 모드로 변경

  3. 전체 삭제 기능이 있어야 함

  • 정말 막힘 없이 진행되다 딱 한 군데에서 문제를 맞았습니다. 상황에 맞게 알림을 띄우는 기능이었는데, JS에서는 아무런 문제 없이 해결했던 이 기능을 React에서 구현 하려고 하니 이상한 문제가 발생하더군요. 여러 동작을 해서 메시지가 많이 발생할 경우, 메시지가 예시처럼 모두 생성되는 것이 아니라 같은 자리에서 텍스트만 바뀌어서 출력이 되었습니다. 물론 잘 해결해서 과제를 마쳤습니다.


회고

워밍업 스터디의 2주 차에 진입하며 자바스크립트 공부를 마치고 새롭게 React 공부를 진행하며 React의 다양한 기술을 접하게 되었습니다. 특히 state, props, hook, 그리고 라우팅 부분은 처음에는 이해하기가 어려웠습니다. 그러나 부족한 이해를 보완하기 위해서 강의 내용을 정리하고, 추가적인 학습 자료를 찾아가며 개념을 확실히 파악하려고 노력했습니다.

React 학습을 마치고 시작한 미션도 초반에는 막막함을 느꼈지만 코드를 작성해 나가며 수업 때 배운 내용을 적용해 가며 문제를 해결해 나갔습니다.

워밍업 스터디도 이제 끝을 향해 가네요. 마무리되는 날까지 열심히 학습에 참여하고 미션 해결을 위해 도전해 보겠습니다. 

댓글을 작성해보세요.

  • John Ahn
    John Ahn

    보경님 위에부터 전체 다 읽어봤는데 저도 복습이 잘되더라고요!! 너무 정리를 잘하셨습니다!

    혹시나 이해가 안 되는 부분은 그 해당 부분을 배속으로 다시 들어도 좋을 것 같습니다.

    항상 공부할 때 이런 식으로 정리하면서 하시면 너무 좋을 것 같습니다! 파이팅입니다!

채널톡 아이콘