https://ko.react.dev/learn/thinking-in-react

 

정리하자면,,,

State 는 어떤걸로?

  • 시간이 지나면서 변할 수 있음
  • props로 받는거 아님
  • 계산 가능 아님 ( vue에서 computed 에 들어가는건 아님.. 이렇게 이해)

State 어디에 위치?

  • 공통부모
  • 공통부모가 없다면 그 상위
  • 아니면 새로 상위 컴포넌트 만들어서
더보기

이 중 어떤 게 State가 되어야 할까요? 아래의 세 가지 질문을 통해 결정할 수 있습니다.

  • 시간이 지나도 변하지 않나요? 그러면 확실히 State가 아닙니다.
  • 부모로부터 Props를 통해 전달됩니까? 그러면 확실히 State가 아닙니다.
  • 컴포넌트 안의 다른 State나 Props를 가지고 계산 가능한가요? 그렇다면 절대로 State가 아닙니다!

그 외 남는 건 아마 State일 겁니다.

  • 제품의 원본 목록은 Props로 전달되었기 때문에 State가 아닙니다.
  • 사용자가 입력한 검색어는 시간에 따라 변하고, 다른 요소로부터 계산할 수 없기 때문에 State로 볼 수 있습니다.
  • 체크박스의 값은 시간에 따라 바뀌고 다른 요소로부터 계산할 수 없기 때문에 State로 볼 수 있습니다.
  • 필터링된 제품 목록은 원본 제품 목록을 받아서 검색어와 체크박스의 값에 따라 계산할 수 있으므로, 이는 State가 아닙니다.

따라서, 검색어와 체크박스의 값만이 State입니다! 잘하셨습니다! → 이걸보면 입력값을 state로 두면 되려나...?

 

 

State가 어디에 위치해야 하는지 결정

대개, 공통 부모에 State를 그냥 두면 됩니다.

혹은, 공통 부모 상위 컴포넌트에 둬도 됩니다.

State를 소유할 적절한 컴포넌트를 찾지 못했다면, State를 소유하는 컴포넌트를 하나 만들어서 상위 계층에 추가하세요.

 

Props vs State

React는 Props와 State라는 두 개의 데이터 “모델”이 존재합니다. 둘의 성격은 매우 다릅니다.

  • Props는 함수를 통해 전달되는 인자 같은 성격을 가집니다. Props는 부모 컴포넌트로부터 자식 컴포넌트로 데이터를 넘겨서 외관을 커스터마이징하게 해줍니다. 예를 들어, Form은 color라는 Prop을 Button으로 보내서 Button을 내가 원하는 형태로 커스터마이징할 수 있습니다.
  • State는 컴포넌트의 메모리 같은 성격을 가집니다. State는 컴포넌트가 몇몇 정보를 계속 따라갈 수 있게 해주고 변화하면서 상호작용Interaction을 만들어 냅니다. 예를 들어, Button은 isHovered라는 State를 따라갈 것입니다.
  • Props와 State는 다르지만, 함께 동작합니다. State는 보통 부모 컴포넌트에 저장합니다. (그래서 부모 컴포넌트는 그 State를 변경할 수 있습니다.) 그리고 부모 컴포넌트는 State를 자식 컴포넌트에 Props로서 전달합니다. 처음 봤을 때 둘의 차이를 잘 알기 어려워도 괜찮습니다. 약간 연습이 필요할 거예요!

리렌더링?

  • React는 다른부분만 리렌더링 → 최적화
  • 모든 이벤트 핸들러가 실행되고 set 함수를 호출 한 후 화면 업데이트 → 단일 이벤트 중 여러번 리렌더링 방지 
더보기

사용자가 제공한 새로운 값이 Object.is에 의해 현재 state와 동일하다고 판정되면, React는 컴포넌트와 그 자식들을 리렌더링하지 않습니다.

이것이 바로 최적화입니다. 경우에 따라 React가 자식을 건너뛰기 전에 컴포넌트를 호출해야 할 수도 있지만, 코드에 영향을 미치지는 않습니다.

 

React는 state 업데이트를 batch 합니다. 모든 이벤트 핸들러가 실행되고 set 함수를 호출한 후에 화면을 업데이트합니다. 이렇게 하면 단일 이벤트 중에 여러 번 리렌더링 하는 것을 방지할 수 있습니다. 드물지만 DOM에 접근하기 위해 React가 화면을 더 일찍 업데이트하도록 강제해야 하는 경우, flushSync를 사용할 수 있습니다.

 

출처: <https://ko.react.dev/reference/react/useState>

 

 


라이프사이클

 

  • mount(생성): useEffect(() => { ... }, [])
  • update(업데이트): useEffect(() => { ... }, [의존값])
  • unmount(소멸): useEffect 반환 함수(return () => { ... })
import { useEffect } from "react"; 
 
function MyComponent() { 
  useEffect(() => { 
    // (생성/마운트 - created, mounted) 
    // 여기에 API 호출 등 
    console.log('컴포넌트가 마운트됨'); 
 
    return () => {  
      // (언마운트 - destroyed) 
     // 이벤트 제거, 타이머 해제 등  
      console.log('컴포넌트가 소멸됨'); 
    }; 
  }, []); // 의존성 배열 - 빈 배열이면 최초/언마운트만 실행됨 
     
  return <div>Hello</div>; 
}

JSX

https://ko.react.dev/learn/writing-markup-with-jsx

 

 

 

1.하나의 루트 엘리먼트로 반환하기

JSX는 HTML처럼 보이지만 내부적으로는 일반 JavaScript 객체로 변환됩니다.

하나의 배열로 감싸지 않은 하나의 함수에서는 두 개의 객체를 반환할 수 없습니다. 

따라서 또 다른 태그나 Fragment로 감싸지 않으면 두 개의 JSX 태그를 반환할 수 없습니다.

2. 모든 태그는 닫아주기 (당연한거 아닌가..?)

3. 거의 대부분 캐멀 케이스로 작성


양방향 바인딩

vue에서 v-model 인것

  • 값 상태 관리와 변경 이벤트가 분리되어 있음
import { useState } from "react"; 
 
function MyInput() { 
  const [text, setText] = useState(''); 
  return ( 
    <input  
      value={text} 
      onChange={e => setText(e.target.value)} 
    /> 
  ); 
}

리액티브 vs. 선언적, 불변성 관리

Vue

  • Vue는 this.message = "hi"처럼 변경하면 자동으로 UI가 갱신
  • 배열/객체 변화도 내부적으로 Proxy로 추적

React

  • "불변성 유지"를 아주 중요하게 여김!
  • 상태 변경 시 새로운 객체 생성 필요.
    (기존 객체나 배열 수정 X, 꼭 새로운 레퍼런스 할당 O)
  • 주요 이유: 레퍼런스가 달라져야 리렌더링이 발생함
  • 객체도 마찬가지로 spread (...) 문법 등으로 새 객체를 만들어야 함.

    즉, 상태(객체/배열) 변경 시 무조건 "복제 → 새롭게 할당" 방식이 기본!
// 잘못된 예시 (직접 수정) 
const [arr, setArr] = useState([1,2,3]); 
arr.push(4);          // X (불변성 위배) 
setArr(arr); 
   
// 올바른 예시 (새 배열 할당) 
setArr(prev => [...prev, 4]);   // O

 


컴포넌트 통신

vue 에서는 

  • 부모 → 자식 props로 전달
  • 자식 → 부모 $emit

React에서는

  • 부모 → 자식 props로 전달
  • 자식 → 부모 자식이 콜백 실행 

 

  • React는 emit과 같은 전용 시스템이 없습니다.
  • 자식이 부모에게 데이터를 전달하려면, "부모가 함수(콜백)를 props로 주고, 자식이 그 함수를 호출"하는 방식입니다.
  • 컴포넌트 간 state 이동은 props를 통해, cross-parent 간 이동은 상태 끌어올리기(lifting state) 방법을 자주 사용합니다.
import React, { useState } from 'react'; 
   
function Child({ msg, onMsg }) { 
  return ( 
    <div> 
      <div>자식에서 받은 값: {msg}</div> 
      <button onClick={() => onMsg('자식에서 클릭됨!')}> 
        부모에 이벤트 알리기 
      </button> 
    </div> 
  ); 
} 
   
function Parent() { 
  const [parentMsg] = useState('부모의 메시지!'); 
   
  const handleChildMsg = (childMsg) => { 
    alert('자식에서 받은 메시지: ' + childMsg); 
  }; 
   
  return ( 
    <div> 
      <Child msg={parentMsg} onMsg={handleChildMsg} /> 
    </div> 
  ); 
} 
   
export default Parent;

 

상태관리

Vue

  • Vuex (store, module, action/mutation 등)

React

  • 복잡도/팀 규모에 따라 매우 다양함
  • Context API: 간단한 전역상태 마치 Vue의 provide/inject 느낌
  • 외부 라이브러리:
    • Redux: 전통적, 정말 많은 기능(개발툴, 미들웨어)
    • Recoil / Zustand: 간단한 글로벌 state
    • MobX: 리액티브한 구조, 데코레이터 기반

      상태 관리 패턴
  • 기본적으로 "props-drilling"(여러 단계 props 전달)이 발생하면 Context나 외부 상태관리 사용 고려
  • 작은 프로젝트 → useState/Context 조합으로도 충분
  • 프로젝트가 커지면 Redux, Recoil, Zustand 등 사용 → 우리는 redux 사용 

1. Context API

1) 개념

  • React 공식 내장 기능(외부 라이브러리 불필요)
  • 컴포넌트 트리 전체에서 전역적으로 데이터를 쉽게 공유
  • 예: "로그인 정보, 테마, 언어" 등 여러 곳에서 반복적으로 쓰이는 값 관리에 적합

2) 원리

  • createContext로 context를 생성
  • Context.Provider로 값(value)을 하위 트리에 "공급(Provide)"
  • 하위 컴포넌트 어디서든 useContext(Context)로 참조 가능
import { createContext, useContext, useState } from 'react'; 
   
const NameContext = createContext(); 
   
function App() { 
  const [name, setName] = useState('철수'); 
   
  return ( 
    <NameContext.Provider value={{ name, setName }}> 
      <Child /> 
    </NameContext.Provider> 
  ); 
} 
   
function Child() { 
  const { name, setName } = useContext(NameContext); 
  // 이렇게 중첩된 컴포넌트 어디서나 쉽게 접근 
  return <input value={name} onChange={e => setName(e.target.value)} />; 
}

 

 

장점

  • 별도 라이브러리 없이 전역 상태 가능
  • 코드량 적음, 간단한 앱에 적합

단점

  • 상태 변경 시 Provider 하위 모든 컴포넌트가 "무조건" 리렌더링됨 (성능 문제)
  • 복잡한 전역 상태나 로딩/비동기 관리엔 한계

 

2. Redux

1) 개념

  • React 중심의 가장 오래되고 유명한 상태 관리 라이브러리
  • 아주 큰 앱(대규모 상태/데이터 흐름, 복잡한 데이터 구조, 액션 추적, 미들웨어 필요 등)에 특화
  • 상태의 일관성, 예측가능성, 시간여행(debugging) 보장

2) 원리

  •  메인 구성요소
    • 상태(state)를 담고있는 중앙 저장소(하나만 존재)
    2. action (액션)
    • "이런 일이 일어났다!"라는 신호(객체/함수)
    • { type: 'INCREMENT', payload: 3 } 처럼 객체
    3. reducer (리듀서)
    • 스토어의 상태를 "어떻게 바꿀지" 결정하는 함수
    • 이전 상태+액션 → 새 상태 반환 (상태를 직접 바꾸지 않고, "복사본 새로 만듦" ★불변성)
  • 1. store (스토어)
  •  Redux 상태 변화 흐름
  1. 액션 발행(dispatch action)
    • 사용자가 버튼 클릭 → "INCREMENT"라는 액션을 발생시키도록 코드가 dispatch
  2. 리듀서(reducer)가 호출됨
    • 현재 상태와 액션을 인자로 받아 새로운 상태를 '리턴'
  3. 스토어(store)가 새 상태로 변경
    • 변경된 상태를 스토어가 갖게됨
  4. React 컴포넌트는 구독해 두었다가, 상태가 바뀌면 자동으로 리렌더링됨

장점

  • 아주 안정적, 예측가능/디버깅 최상, 미들웨어 확장 쉬움
    (로깅, API관리, 비동기 등 커스텀 작업풍부)
  • 레거시 기업/대형서비스에서 매우 많이 사용

    단점
  • 코드량 많음/복잡(boilerplate), 작은 앱에 불필요하게 무거움
  • 리듀서/액션/스토어/미들웨어 등 개념이 많아서 진입장벽 있음

vue 에서 code Mirror를 쓰는 방법과

더나아가 MaxLength를 설정하는 방법을 알아보좌!

 

 

1. Code mirror 설치

npm install vue-codemirror --save

 그러면 package.json에 codemirror가 추가된걸 확인 할 수 있다.

 

2. main.js 에 선언

import Codemirror  from 'vue-codemirror';

Vue.use(Codemirror);

 

3. code mirror 컴포넌트 사용

<codemirror v-model="query" :options="options" ref="codeMirror" />

v-model에는 codemirror에 입력되는 값을 변수 선언한 뒤 넣어주고

다양한 모드와 옵션이 있는데 그건 부모 컴포넌트에서 options 로 선언해주고 props로 내려주면된다.

 

나같은 경우에는 query를 입력하는 창이라 관련되어서 옵션을 data에 선언해 주었다.

 

예)

                options: {
                    tabSize: 4,
                    styleActiveLine: true,
                    mode: 'text/x-sql',
                    lineNumbers: true,
                    line: true,
                    lineWrapping: true,
                    theme: 'default'
                },

 

더 다양한 옵션은 다음을 참고해보자

https://github.com/surmon-china/vue-codemirror#readme

 

GitHub - surmon-china/vue-codemirror: ⌨️ @codemirror component for @vuejs

⌨️ @codemirror component for @vuejs. Contribute to surmon-china/vue-codemirror development by creating an account on GitHub.

github.com

 

4. max Length 설정하기

options에 따로 max Length 관련 지원이 없어서 직접 구현하였다.

 

먼저 기존 options에 maxLength라고 선언해준다.

                options: {
                    tabSize: 4,
                    styleActiveLine: true,
                    mode: 'text/x-sql',
                    lineNumbers: true,
                    line: true,
                    lineWrapping: true,
                    theme: 'default',
                    maxLength: 4000,
                },

 

그 뒤 , 다음 링크를 참고하여 구현 하였다.

https://github.com/codemirror/CodeMirror/issues/821

 

MaxLength attribute · Issue #821 · codemirror/CodeMirror

Is there a way to set a MaxLength number of characters allowed?

github.com

 

 

codemirror 소스를 보니 모든 event가 발생하면 emit 해주고 있었다.

 

        const allEvents = [
          'scroll',
          'changes',
          'beforeChange',
          'cursorActivity',
          'keyHandled',
          'inputRead',
          'electricInput',
          'beforeSelectionChange',
          'viewportChange',
          'swapDoc',
          'gutterClick',
          'gutterContextMenu',
          'focus',
          'blur',
          'refresh',
          'optionChange',
          'scrollCursorIntoView',
          'update'
        ]
        .concat(this.events)
        .concat(this.globalEvents)
        .filter(e => (!tmpEvents[e] && (tmpEvents[e] = true)))
        .forEach(event => {
          // 循环事件,并兼容 run-time 事件命名
          this.cminstance.on(event, (...args) => {
            // console.log('当有事件触发了', event, args)
            this.$emit(event, ...args) // EMIT !!!!
            const lowerCaseEvent = event.replace(/([A-Z])/g, '-$1').toLowerCase()
            if (lowerCaseEvent !== event) {
              this.$emit(lowerCaseEvent, ...args)
            }
          })
        })

 

그래서 부모 컴포넌트에서 codeMirror를 사용하는쪽에서 emit 하는 function을 받아주었다.

<codemirror v-model="query" :options="options" ref="codeMirror" @beforeChange="enforceMaxLength" />

 그리고 다음과 같이 더이상 입력되지 않도록 function 을 선언해주었다. 

            enforceMaxLength(cm, change) {
                let maxLength = cm.getOption('maxLength');
                if (maxLength) {
                    let str = change.text.join('\n');
                    let delta = str.length - (cm.indexFromPos(change.to) - cm.indexFromPos(change.from));
                    this.cmLength = cm.getValue().length + delta;
                    if (this.cmLength > maxLength) {
                        this.cmLength = maxLength;
                    }
                    delta = cm.getValue().length + delta - maxLength;
                    if (delta >= 0) {
                        str = str.substr(0, str.length - delta);
                        change.update(change.from, change.to, str.split('\n'));
                    }
                }
                return true;
            },

 

그리고 현재 length를 세어주기 위해 cmLength를 선언해주고 length를 넣어주었다. 

 

vue 개발을 하다가 data의 값이 잘 바꼈는데

컴포넌트 rerendering이 잘안돼서 그대로 이상한 값이 남아 있는 경우가 있었다.

 

그래서 찾아보니 좋은 stackOverFlow를 발견..

강제로 rerendering을 시켜주고 싶었다!

 

https://stackoverflow.com/questions/32106155/can-you-force-vue-js-to-reload-re-render

 

Can you force Vue.js to reload/re-render?

Just a quick question. Can you force Vue.js to reload/recalculate everything? If so, how?

stackoverflow.com

 

 


이중에 어떤 방법을 할까하다가

 

처음엔 당연히

this.$forceUpdate()를 쓰려고 했지만 잘안돼서.. 그치만 다시 찬찬히 읽어보니 방법이 생각났지만

여기 나온 방식중에서 가장 Best way라고 나온  key를 이용하는 방식을 써서 해결하였다.

 

그럼 forceUpdate와 Key를 이용해서 강제 rerendering하는 것을 알아보자 

(https://michaelnthiessen.com/force-re-render/ 다음을 해석하였습니다. 잘쓰여져있네여)

 


1. forceUpdate();

이것은 vue에서 공식적으로 제공해주는 방식이다. (https://vuejs.org/v2/api/#vm-forceUpdate)

 

보통은 Vue에서 data의 변화를 감지하는데 이것을 보통 reactive 하다고 말합니다. 

그런데 아시다시피 Vue의 reactivity 시스템은 모든 변화를 감지하지 못합니다..(언제그러는지는 정확하게 모르겠으나 개발하다보면 왕왕 있습니다..)

 

그래서 변화를 감지하지 못할때 강제 리렌더링을 해줍니다.

 

forceUpdate를 하기위해서는두가지 방법이 있는데 , Global하게 forceUpdate 하는 것과component 단위로 forceUpdate 하는방법

 

단순하게 생각해도 Global한 방식은 잘안쓸거 같군요..

// Globally
import Vue from 'vue';
Vue.forceUpdate();

// Using the component instance
export default {
  methods: {
    methodThatForcesUpdate() {
      // ...
      this.$forceUpdate();  // Notice we have to use a $ here
      // ...
    }
  }
}

 

 

2. key 이용

 

key를 이용 하려면 Vue에서 제공하는 key에 대해서 알아야 하는데

 

Vue에서 key는 각 특정 component에 특정 값을 매핑해 두는데 이게 key라고 볼 수 있다.

그래서, key가 동일하면 component가 변하지 않지만

key가 변하면 Vue는 예전 component 를 지우고 새로운 component를 만듭니다.

 

 

<template>
   <component-to-re-render :key="componentKey" />
</template>

<script>
 export default {
  data() {
    return {
      componentKey: 0,
    };
  },
  methods: {
    forceRerender() {
      this.componentKey += 1;  
    }
  }
 }
</script>

그래서 다음과같이 자식 component에 key를 props로 내려주고

forceRerender라는 method에 key값을 변경해주도록 해주면

 

강제 Rerendering이 필요할때 forceRerender() method를 호출해주면

Vue에서 이전 component 를 지우고 새 component를 그려줍니다.

 

저도 이방법으로 쓰레기값이 남아있는 문제를 해결하였습니다!

 

굳굳!

 

 

이번에 vue.js 개발을 많이 하게되면서

가장 헷갈리게 된 포인트가 이것이다

 

언제 computed를 쓰고, 언제 watch를 쓰지?

사실 두개다 얻어지는 결과값은 엇비슷하기 때문이다

 

그래서 이 고민을 많이 했다

그러면 더 이상 고민하지 않도록 정리보도록 하쟈 ㅎㅎ

 

 

예제도 쓰면 좋지만 ㅎㅎ vue 공식문서에 있는것을 보면 될듯 싶다.


1. computed : 반응형 getter

- 사용하기 편하고, 자동으로 값을 변경하고 캐싱해주는 반응형 getter

- 선언형 프로그래밍 방식

- 계산된 결과가 캐싱된다. computed 속성은 해당 속성이 종속된 대상이 변경될때만 함수를 실행한다

  • 이는 method와 다른 점이다. method를 호추하면, 렌더링을 할 때마다, 항상 함수를 실행한다.

- computed는 computed에 새로운 프로퍼티를 추가해 주어야한다. data와 혼재에서 사용 불가

 

2. watch : 반응형 콜백

- 프로퍼티가 변경될 때, 지정한 콜백 함수가 실행 : 반응형 콜백

- data에 새로 프로퍼티를 추가 할 필요 없고, 명시된 프로퍼티를 감시할 것이다~를 표현해주면된다.

- vue 공식 문서에, computed와 watch 둘다 가능 한 것은 computed를 쓰라고 명시되어 있다.

  • 선언형 프로그래밍이 명령형 프로그래밍보다 코드 반복 및 우수해서

 

 

3. 언제쓸까?

1) data 에 할당 된 값들 사이의 종속관계를 자동으로 세팅 : computed

2) 프로퍼티 변경시점에 action이 필요할때 (api call , router ...) : watch

3) computed는 종속관계가 복잡할 수록 재계산시점을 알 수 없어서 종속관계의 값을 리턴하는것 이외에는 코드 지양

4) computed와 watch 둘다 가능 한 것은 computed

 

5) data의 실시간/빈번한 update가 필요한것은 watch,  한번 렌더링 되었을때만 update되면 되는것은 computed

 좀 애매할 수도 있지만 실제로 개발하면서 느꼈던 것이다.

여러 컴포넌트들 사이의 부모-자식관계가 있는데 이와중에 받은 props를 또 자식에서 변경하거나, 하는 요건이 많은데

페이지를 처음 렌더링 할때 1번만 해당 data를 update하면 된다면 compted를

계속해서 다른 컴포넌트 사이의 정보가 업데이트 되면서 해당 정보가 현재 달라졌는지? 실시간으로 보아야한다면 watch를 써 주었다.

 

6) computed는 이미 정의된 계산식에 따라 결과값을 반환할때,

  watch는 특정한 어똔조건에서 함수를 실행시키기 위한 트리거로 사용 가능

 

 

 

 

 


참고

kr.vuejs.org/v2/guide/computed.html

medium.com/@jeongwooahn/vue-js-watch%EC%99%80-computed-%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%99%80-%EC%82%AC%EC%9A%A9%EB%B2%95-e2edce37ec34

Front end Framework 3개중 고민하는 사람이 많을 것으로 생각한다.

예전에는 Angular가 대세였지만 요새는 React와 Vue 중에서 고민을 하는거같다

 

그래서 세개를 다양한 관점에서 비교해 봤다.

해당 사이트를 요약해서 표로 만들어 봤다 

(https://hackernoon.com/angular-vs-react-vs-vue-which-is-the-best-choice-for-2019-16ce0deb3847)

 

 

  Angular React Vue
Popularity 3. 1. 2.
Introduction to the background Released in 2010
by Google
Released in 2013
by Facebook
Released in 2014 by Evan you who is an ex-engineer
of Google
Performance Uses real DOM Uses virtual DOM Uses virtual DOM
Use Cases Google, The Guardian , Weather.com Facebook, Twitter, Whatsapp, Instagram 9Gag, GitLab
Framework size Heavyweight application Suitable for light-weight application The lightest
Learning curve 3. need to learn typescript 1?? 2??
Flexibility Offers you need
but not much flexible
The most flexible Not much opinionated
or flexible
Component based Yes Yes Yes

 

 

결론은,

Vue and React offers better performance and flexibility than Angular.

Vue and react are more suited for light-weight applications and angular is the best for large UI applications.

Angular is highly opinionated and unlike Vue and React, it offers everything
from routing, templates to testing utilities in its package.

Vue is the most popular, loved and growing framework of Javascript.

 

 

개인적인 의견을 붙여보자면,

저는 vue만 해봤는데 ..

해당 사이트에서는 react가 learning curve가 제일 낮다고 했지만 내 생각엔 vue가 제일 낮은거 같다

react를 안해봤지만 vue를 상당히 쉽게 배웠기 때문이다

 

react를 해보고 다시 리뷰해보자 ㅎㅎ '-'

react는 React Native가 있어 모바일앱을 구현하기 좋아서 모바일을 커버하고 싶다면 

react를 선택하는것도 좋을거 같다

 

 

그럼이만

 

 

+ Recent posts