ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [ReactJS] VUE개발자 REACT(함수형)에 빠르게 적응하기 ( 비교 개발 ) - 스크롤 압박 내용 매우 많음, 매우 유용
    frontend/react&next 2022. 3. 4. 13:26

    오늘은 Vue 개발만 하던 저가 React를 배울 때 어떻게 최대한 빠르게 적응했는지에 대해서 동작에 대해서 비교하면서 설명드리려고 합니다. 이 방식은 저가 겪었던 방식으로 여타 프레임워크를 사용하지 않았던 분들에겐 부적합 할 수 있으니 그냥 이사람은 이렇게 넘어갔구나하고 생각해주시면 감사하겠습니다. 물론 저는 vue( 이젠 3 CompositionAPI )와 react 두개를 병행 하고 있습니다. cli와 cra를 기반으로 설명드리겠습니다.

     

    1. 앱 기동시 첫 진입

    - vue-cli ( main.js )

    import { createApp } from 'vue'
    import App from './App.vue'
    
    createApp(App).mount('#app')

    - create-react-app ( index.js )

    import React from 'react';
    import ReactDOM from 'react-dom';
    import App from './App';
    
    const render = ReactDOM.render;
    
    render(<App />, document.getElementById("root"));

    앱의 초기 호출이 되는 부분입니다. create-react-app은 정말 앱실행에 필요한 부분 빼고는 걷어낸 상태 입니다. 이것을 봣을때는 크게 차이가 없습니다. 둘다 App.vue(Vue)/App.js(React) 컴포넌트를 import해서 vue는 #app 하위에 react는 #root 하위에 렌더링 시켜줍니다. 진짜로 똑같이 맞추고싶으면 react public 에 <div id="root"></div>를 app으로 바꿔서 하시면 됩니다.

     

    2. 최상위 컴포넌트

    - vue (App.vue)

    <template>
      <h1>안녕하세요</h1>
    </template>
    
    <script>
    import "./App.css";
    export default {
      name: "App",
    };
    </script>
    
    <style>
    //내부에 작성하여도됨
    </style>

    - react ( App.js )

    import "./App.css";
    
    const App = () => {
      return <h1>안녕하세요</h1>;
    }
    
    export default App;

    기본적으로 큰 구조는 다르지 않습니다. vue파일 내부에서는 <template></template> 안에 마크업을 작성하여 주시면 되고 react에서는 함수의 return 에 마크업을 작성하시면 됩니다. vue는 크게 template , script, style로 나누어져 있는 반면 react는 스크립트 기반으로 작성되어 있습니다. vue composition api에서는 react처럼 변합니다. 점점 비슷해져가고 있는 것 같습니다.

     

    3. 데이터 ( 상태 / 로컬 변수 )

    - vue

    <template>
      <div>
        <h1>{{count}}</h1>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      data() {
        return {
          count: 0,
        };
      },
    };
    </script>

    - react

    import { useState } from "react";
    
    const App = () => {
      const [count, setCount] = useState(0);
      return (
        <div>
          <h1>{count}</h1>
        </div>
      );
    }
    
    export default App;

    vue에서는 data()에서 객체를 리턴해서 상태를 관리하지만, react에서는 useState라는 react의 Hook을 이용하여 상태를 관리합니다. 왜 굳이 react는 훅을 써서 state를 관리하냐고 하면 state의 저장방식이 객체이기 때문입니다. 그래서 state 내부의 값을 직접 수정하여도 객체의 메모리 주소가 변하지 않아서 값이 변경되지 않은 것으로 판단하고 update 시키지 않습니다. 그래서 값을 직접 변경하지 않고 새로운 객체를 만들어서 할당하는 것으로 변경해야 되며 이를 setState, useState를 이용해서 구현합니다. 오늘은 비교해서 보는 것임으로 이까지 설명하고 바로 state 변경을 해보도록 하겠습니다.

     

    4. 데이터 UPDATE( 상태 / 로컬 변수 )

    - vue

    <template>
      <div>
        <h1>{{ count }}</h1>
        <button @click="plus">숫자올려</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        plus() {
          this.count++;
        },
      },
    };
    </script>

    - react

    import { useState } from "react";
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      const plus = () => {
        setCount(count + 1);
      };
      
      return (
        <div>
          <h1>{count}</h1>
          <button onClick={plus}>숫자 올려</button>
        </div>
      );
    }
    
    export default App;

    일단 업데이트를 하기 위해서는 업데이트를 위한 메소드를 만들어야 합니다. vue는 위에서 보시다 싶히 vue에서 지정한 문법인 methods:{}내부에 plus() 함수를 만들어줍니다. react는 useState의 set을 이용하여 plus함수를 만들어 줍니다. 여기서 확연한 차이가 있습니다. 이것을 사용하는 방법도 조금의 문법 차이가 있습니다. vue는 @[이벤트명] 형식으로 명시하고 react는 on[이벤트명(대문자 시작)]으로 명시합니다. 위의 두 코드는 같은 동작을 하며 숫자를 증가시키게 될 것입니다.

     

    5. V-MODEL

    - vue

    <template>
      <input type="text" v-model="test" />
    </template>
    
    <script>
    export default {
      name: "App",
      data() {
        return {
          test: "",
        };
      },
    };
    </script>

    - react

    import { useState } from "react";
    
    function App() {
      const [test, setTest] = useState("");
      return (
        <input
          type="text"
          value={test}
          onChange={(e) => {
            e.preventDefault();
            setTest(e.target.value);
          }}
        />
      );
    }
    
    export default App;

    Vue는 간단히 v-model을 통해서 input과 data를 양방향 바인딩할 수 있지만, react에서는 state와 연동하기 위해서 setTest를 통해서 e.target.value(현재 타겟(input)의 값)의 값을 test에 할당하는 작업을 onChange시 마다 호출하여 주어야 됩니다.

     

    6. WATCH

    - vue

    <template>
      <div>
        <h1>{{ count }}</h1>
        <button @click="plus">숫자올려</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      watch: {
        count() {
          console.log("변경완료");
        },
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        plus() {
          this.count++;
        },
      },
    };
    </script>

    - react

    import { useState, useEffect } from "react";
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        if (count === 0) return;
        console.log("변경완료");
      }, [count]);
    
      const plus = () => {
        setCount(count + 1);
      };
    
      return (
        <div>
          <h1>{count}</h1>
          <button onClick={plus}>숫자 올려</button>
        </div>
      );
    }
    
    export default App;

    vue에는 data의 값의 변경을 감지하여 특정 동작을 하게 해주는 watch라는 것이 있습니다. 물론 성능을 위해서 남용하면 안되지만 이것을 우리는 react에서도 구현할 수 있습니다. react에는 useEffect라는 Hook이 있습니다. 해당 Hook은 컴포넌트가 mounted 되었을 때와 두번째 인자인 []안의 값이 변경 될때 동작을 수행하는 Hook입니다. 우리는 이 Hook을 이용해서 watch와 같은 동작을 위의 코드처럼 구현할 수 있습니다.

     

    7. COMPUTED

    - vue

    <template>
      <div>
        <h2>{{ plus2 }}</h2>
        <h1>{{ count }}</h1>
        <button @click="plus">숫자올려</button>
      </div>
    </template>
    
    <script>
    import AppTitle from "@/components/AppTitle.vue";
    
    export default {
      name: "App",
      computed: {
        plus2() {
          return this.count + 3;
        },
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        plus() {
          this.count++;
        },
      },
    };
    </script>

    - react

    import { useState, useMemo } from "react";
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      const plus2 = useMemo(() => count + 3, [count]);
    
      const plus = () => {
        setCount(count + 1);
      };
    
      return (
        <div>
          <h2>{plus2}</h2>
          <h1>{count}</h1>
          <button onClick={plus}>숫자 올려</button>
        </div>
      );
    }
    
    export default App;

    vue에는 어떤 종속된 대상을 캐싱하여 값을 출력하는 comptued라는 것이 있습니다. 종속된 대상의 값이 변할때 연산이 되는 것입니다. methods로 일일이 호출 할때 값을 변화시키는 것이 아니라 값의 변화를 보고 그에 맞게 연산을 통해 리턴해주는 것입니다. react도 이런 작업을 useMemo라는 Hook을 통해 작업할 수 있습니다. useMemo는 첫번쨰 파라미터에 연산함수를 넣어주고 두번째 배열에는 감지할 상태를 넣습니다. 이 안에 값 위에서는 count겠죠? 이것이 바뀌면 연산을 다시합니다. 그 외에는 이전에 연산한 값을 재사용하여 리턴하여 줍니다. 이처럼 자주 연산이 복잡하고 자주 필요한 것은 함수로 빼놓지 않고 위와 같이 computed , useMemo를 사용하는 것이 좋습니다.

     

    이까지 상태들에 대해서 좀 다루어 보았으니 아래부터는 컴포넌트에 대해서 다루어 보겠습니다.

     

    8. 컴포넌트 ( COMPONENTS )

    - vue

    컴포넌트 ( AppTitle.vue )

    <template>
      <h1>타이틀</h1>
    </template>
    
    <script>
    export default {
      name: "app-title",
    };
    </script>

    메인 ( App.vue )

    <template>
      <div>
        <AppTitle />
        <h1>{{ count }}</h1>
        <button @click="plus">숫자올려</button>
      </div>
    </template>
    
    <script>
    import AppTitle from "@/components/AppTitle.vue";
    
    export default {
      name: "App",
      components: {
        AppTitle,
      },
      data() {
        return {
          count: 0,
        };
      },
      methods: {
        plus() {
          this.count++;
        },
      },
    };
    </script>

    - react

    컴포넌트 ( AppTitle.js )

    const AppTitle = () => {
        return <h1>타이틀</h1>
    }
    export default AppTitle;

    메인 ( App.js )

    import AppTitle from "./components/AppTitle";
    import { useState } from "react";
    
    const App = () => {
      const [count, setCount] = useState(0);
    
      const plus = () => {
        setCount(count + 1);
      };
    
      return (
        <div>
          <AppTitle/>
          <h1>{count}</h1>
          <button onClick={plus}>숫자 올려</button>
        </div>
      );
    }
    
    export default App;

    위에서 보시다 싶히 각자 컴포넌트를 생성 해주고, 이것을 메인에 붙일 때는 Vue는 import 후에 export default 내부에 components : {} 객체 안에 선언하여 줘야 됩니다. 하지만 React는 그런 것 없이 import 후 바로 붙여넣기 하여 사용할 수 있습니다. 이렇게 비교해놓고 보니 리액트가 좀 더 간결하네요. 이제 데이터 전달에 대해서 보겠습니다.

     

    9. 컴포넌트간 이벤트 호출 및 데이터 전달(상위->하위)

    - vue ( parent )

    <template>
      <div>
        <AppTitle title="이것은 타이틀" ref="title" />
        <button @click="callChildren">하위 컴포넌트 이벤트</button>
      </div>
    </template>
    
    <script>
    import AppTitle from "@/components/AppTitle.vue";
    
    export default {
      name: "App",
      components: {
        AppTitle,
      },
      methods: {
        callChildren() {
          this.$refs.title.sayTitle();
        },
      },
    };
    </script>

    - vue ( children )

    <template>
      <h1>{{ title }}</h1>
    </template>
    
    <script>
    export default {
      name: "app-title",
      props: {
        title: String,
      },
      methods: {
        sayTitle() {
          console.log(this.title);
        },
      },
    };
    </script>

    - react ( parent )

    import AppTitle from "./components/AppTitle";
    import { useRef } from "react";
    
    function App() {
      const ref = useRef();
      return (
        <div>
          <AppTitle title={"이것은 타이틀"} ref={ref} />
          <button onClick={() => ref.current.sayTitle()}>하위 컴포넌트 이벤트</button>
        </div>
      );
    }
    
    export default App;

    - react ( children )

    import { forwardRef, useImperativeHandle } from "react";
    
    const AppTitle = forwardRef((props, ref) => {
        
      function sayTitle() {
        console.log(props.title);
      }
      useImperativeHandle(ref, () => ({
          sayTitle
      }));
    
      return <h1>{props.title}</h1>;
    });
    export default AppTitle;

    데이터 전달 : 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달 하기 위해서는 둘다 props라는 것을 이용 합니다. vue와 react의 parent 코드를 보시면 AppTitle 컴포넌트에 title="이것은 타이틀" 속성을 주었는데 이것을 child 컴포넌트에서 받아서 사용하게 됩니다. vue의 코드에서는 export default {} 내부에 props 라는 객체가 있는데 props에 받을 데이터를 명시해줍니다. props 객체에 배열로 문자열로 props들을 쭉 넣어도 되지만 위와 같이 형식을 지정해주면 좀 더 코드를 보는 입장에서는 명확해지니깐 저 방식을 추천드립니다. react는 하위 컴포넌트의 파라미터에 props내부에 들어 있으니 그대로 꺼내 쓰시면 됩니다.

    이벤트 호출 : 부모 컴포넌트에서 자식 컴포넌트의 이벤트를 호출하기 위해서는 둘다 ref를 사용하게 됩니다. vue는 컴포넌트에 ref라고 명시하여 쉽게 사용할 수 있고, react에서는 useRef라는 Hook을 이용하여 구현할 수 있습니다. 둘다 위와 같이 ref를 할당하고 나면 vue에서는 this.$refs.[래퍼런스명].[함수명]() 으로 함수를 호출 할 수 있으며, react에서는 ref.current.[함수명]() 으로 호출 할 수 있습니다.

     

    이 외에도 , 함수 자체를 바인딩 해서도 쓸 수 있습니다. vue의 경우 props에 Function이라고 선언해서 사용하시고, react의 경우에는 값을 전달할 때와 같이 똑같이 하시면 됩니다.

     

    10. 컴포넌트간 이벤트 호출 및 데이터 전달(하위->상위)

    - vue ( parent )

    <template>
      <div>
        <AppTitle title="이것은 타이틀" @fromChild="hi" />
      </div>
    </template>
    
    <script>
    import AppTitle from "@/components/AppTitle.vue";
    
    export default {
      name: "App",
      components: {
        AppTitle,
      },
      methods: {
        hi(data) {
          console.log(`자식에서 넘긴 데이터 : ${data}`);
        },
      },
    };
    </script>

    - vue ( child )

    <template>
      <div>
        <h1>{{ title }}</h1>
        <button @click="sayTitle">상위컴포넌트 호출</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "app-title",
      props: {
        title: String,
      },
      methods: {
        sayTitle(/** e */) {
          //e.target.value : input 데이터 넘길 떄
          this.$emit("fromChild", "콜렉트");
        },
      },
    };
    </script>

    - react ( parent )

    import AppTitle from "./components/AppTitle";
    
    function App() {
      const parentFunction = (data) => {
        console.log(data)
      }
      return (
        <div>
          <AppTitle title={"이것은 타이틀"} functionProp={parentFunction}/>
        </div>
      );
    }
    
    export default App;

    - react ( child )

    const AppTitle = (props) => {
      function sayTitle() {
        props.functionProp("콜렉트");
      }
    
      return (
        <div>
          <h1>{props.title}</h1>
          <button onClick={sayTitle}>상위 컴포넌트 호출</button>
        </div>
      );
    };
    export default AppTitle;

    vue에서는 $emit이라는 함수를 통해서 첫번째 인자로는 상위 컴포넌트 함수와의 연결에 사용할 이름을 지정하고 두번째 인자로는 데이터를 넘기면 됩니다. react에서는 props.연결에사용할이름 을 통해서 연결해 줄 수 있습니다. 이벤트를 호출 할 수 도 있고 이 방법을 통해서 데이터를 전달할 수 도 있습니다.

     

    11. 라이프 사이클 ( LIFE CYCLE )

    - vue

    <template>
      <div></div>
    </template>
    
    <script>
    export default {
      name: "App",
      beforeCreate() {},
      created() {},
      beforeMount() {},
      mounted() {},
      beforeUpdate() {},
      updated() {},
      unmounted() {},
    };
    </script>

    - react

    import { useEffect } from "react";
    
    const App = () => {
      useEffect(() => {
        //mouted
        return {
          //unmounted
        };
      }, []);
      return <div></div>;
    };
    export default App;

    vue의 라이프사이클과 react의 라이프사이클인데 react는 함수형으로 개발하면 따로 없기 때문에 useEffect로 구현할 수 있습니다. useEffect내부에 mounted 시점 함수를 작성하고 return 에 unmounted시 동작할 함수를 작성하면 됩니다. vue composition API 기준으로 하면 조금 다를 수 있습니다.

     

    12. 라우팅 ( ROUTING)

    - vue 설치

    npm i vue-router

    - vue router 셋팅 (router/index.js)

    import { createWebHashHistory, createRouter } from "vue-router";
    
    const Home = () => import(/* webpackChunkName: "Home" */ "@/views/Home.vue");
    const Test2 = () => import(/* webpackChunkName: "Test2" */ "@/views/Test2.vue");
    
    const routes = [
      { path: "/", component: Home },
      { path: "/test", component: Test2 },
    ];
    
    export default createRouter({
      history: createWebHashHistory(),
      routes,
    });

    - vue rotuer 사용 (App.vue)

    <template>
      <router-link to="/">Home</router-link>
      <br/>
      <router-link to="/test">TEST</router-link>
      <router-view />
    </template>
    
    <script>
    export default {
      name: "App",
    };
    </script>

    - react 설치

    npm i react-router-dom

    - react router 셋팅

    import { lazy } from "react";
    
    const router = [
      {
        path: "/",
        component: lazy(() => import("../pages/Home")),
      },
      {
        path: "/test2",
        component: lazy(() => import("../pages/TEST2")),
      },
    ];
    
    export default router;

    - react rotuer 사용

    import React from "react";
    import { Routes, Route, Link } from "react-router-dom";
    import router from "../router";
    import { Suspense } from "react";
    const App = () => {
      return (
        <div>
          <Link to="/">Home</Link>
          <br/>
          <Link to="/test2">Test2</Link>
          <Routes>
            {router.map((route, i) => {
              return (
                <Route
                  key={i}
                  path={route.path}
                  element={
                    <Suspense fallback={<></>}>
                      <route.component />
                    </Suspense>
                  }
                />
              );
            })}
          </Routes>
        </div>
      );
    };
    export default App;

    둘다 router를 사용하기 위해서는 vue 는 vue-router 그리고 react는 react-router-dom을 설치하여야 됩니다. 그 후 router하위 index.js에 바뀌는 path와 컴포넌트에 대한 설정을 하고 vue는 router-view를 통해 해당 부분을 보여주게 되고 react는 Routes의 하위 Route에서 보여주게 됩니다. router-view를 풀어서 구현해놓은 느낌으로 사용하실 수 있습니다. 그리고 vue에선 router-link를 통해서 페이지 전환을 하고 react에선 Link를 통해서 페이지 전환을 할 수 있습니다. 둘다 lazy load를 적용해놓았으니 참고하시면 좋습니다.

     

    솔직히 vue와 react는 크게 다르지 않습니다. vue composition API를 사용하면 그 현상은 더욱 커진다고 생각합니다. 거의 같아지고 있다고 생각합니다. 이 외에도 통신을 위한 axios 클라이언트 측면의 전역 상태관리인 Redux, Mobx, Recoil 서버 측면의 상태 관리인 react-query 등의 이해까지 하면 리액트를 개발하시는데 큰 어려움이 없다고 생각됩니다. 비슷하면서도 약간 다른 자바스크립트의 기초만 탄탄하다면 솔직히 react든 vue든 하나만 알고 있으면 나머지 하나를 이해하는건 매우 쉽다고 생각됩니다.

     

    내용이 약간 방대하고 그만큼 빠진내용이 있을 수도 있으니 그래도 많은 도움이 되셨으면 좋겠습니다. 오늘도 하루가 달리 계속 쏟아져나오는 새로운 기술들과 사투중인 프론트엔드 개발자 여러분 화이팅입니다.

    반응형

    댓글

Designed by Tistory.