프론트엔드 개발환경 구축 - React vs Vue
프로젝트/마이에디터 : 나만의 에디터

프론트엔드 개발환경 구축 - React vs Vue

컴포넌트 단위의 개발 방식과 Virtual Dom이라는 이점 때문에 React나 Vue.js를 사용하는 것이 좋겠다는 결론을 내렸습니다. 두 가지 모두 비슷한 장점을 가지고 있고, 성능 면에서도 큰 차이는 없기 때문에, 이후에는 직접 간단한 Todo List를 작성해보며 차이점을 비교해보았습니다.

 

Todo List 작성하기

| 구성

참고 : 벨로퍼트와 함께하는 모던 리액트

 

위 자료를 참고하여 작성했던 React의 Todo List와 거의 동일한 것을 Vue에서도 만들어보았습니다.

Vue vs React

왼쪽이 Vue.js, 오른쪽이 React로 만든 Todo List입니다. 몇 가지 CSS적용을 누락한 것을 제외하면 완전히 똑같이 작동하고 있습니다.

 

리액트는 create-react-app으로, 뷰는 vue-cli로 간단하게 프로젝트를 생성하였고, 각각의 프로젝트 폴더에 component를 위와 같이 추가하였습니다.

그리고 상태관리를 위해 Vue는 Vuex, React는 context API를 사용하였습니다.

 

 

자세한 코드는 생략하고, 가장 차이를 크게 느꼈던 TodoList와 TodoCreate, 상태 관리 방식에 대해서만 살펴보겠습니다.

 

| TodoList

  • Vue
<template>
  <div id="todolist">
    <todo-item v-for="(todo) in todos"
      v-bind:key="todo.key"
      v-bind:todo="todo"
    />
  </div>
</template>

<script>
import TodoItem from './TodoItem.vue'

export default {
  name:'TodoList',
  components: {
    TodoItem
  },
  computed: {
    todos() {
      return this.$store.state.todos;
    }
  }
}
</script>

<style lang="scss">
#todolist {
  flex: 1;
  padding: 20px 32px;
  padding-bottom: 48px;
  overflow-y: auto;
}
</style>

Vue의 경우 모든 component를 위와 같이 <template>, <script>, <style>태그로 구분하여 작성했습니다. 그리고 리스트에 item인 todo를 배치하기 위해, "v-for"를 사용해야만 합니다.

 

  • React
import React from 'react';
import styled from 'styled-components';
import TodoItem from './TodoItem';
import { useTodoState } from '../TodoContext';

const TodoListBlock = styled.div`
  flex: 1;
  padding: 20px 32px;
  padding-bottom: 48px;
  overflow-y: auto;
`;

function TodoList() {
  const todos = useTodoState();

  return (
    <TodoListBlock>
      {todos.map(todo => (
        <TodoItem
          key={todo.id}
          id={todo.id}
          text={todo.text}
          done={todo.done}
        />
      ))}
    </TodoListBlock>
  );
}

export default TodoList;

React는 리스트에 아이템을 배치하기 위해 map을 사용하고 있습니다. Vue가 v-for를 사용해야만 하는 것과 다르게, 이 부분은 for나 forEach등 반복 순회할 수 있는 다른 명령어로도 대체될 수 있습니다.

 

 

| TodoCreate

다음은 새로운 Todo를 추가하는 TodoCreate입니다. 제가 작성한 Todo List는 아래와 같이 모달창을 띄우는 방식으로 작동합니다.

하단의 (+)를 누르면 모달창이 나타난다

 

  • Vue
<template>
  <div>
    <div id="insert-form-positioner" v-if="open">
      <form id="insert-form" v-on:submit="onSubmit">
        <input
        autoFocus
        type="text"
        placeholder="할 일을 입력 후, Enter 를 누르세요"
        v-model="text"
        />
      </form>
    </div>
    <div class="circle-button" :class="{ open: open }" @click="onToggle">
    	<font-awsome-icon icon="plus"/>
    </div>
  </div>
</template>

Vue의 경우, 마찬가지로 v-if를 사용하며, open이 true일 경우 모달창이 보이게 됩니다. 그리고 하단의 (+)버튼을 (x)로 바꾸기 위해 :class로 클래스명을 변경합니다.

 

  • React
<>
  {open && (
    <InsertFormPositioner>
      <InsertForm onSubmit={onSubmit}>
        <Input
        autoFocus
        placeholder="할 일을 입력 후, Enter 를 누르세요"
        onChange={onChange}
        value={value}
        />
      </InsertForm>
    </InsertFormPositioner>
  )}
  <CircleButton onClick={onToggle} open={open}>
    <MdAdd />
  </CircleButton>
</>
const CircleButton = styled.button`
  background: #38d9a9;
  &:hover {
    background: #63e6be;
  }
  &:active {
    background: #20c997;
  }

  // ...생략
  ${props =>
    props.open &&
    css`
      background: #ff6b6b;
      &:hover {
        background: #ff8787;
      }
      &:active {
        background: #fa5252;
      }
      transform: translate(-50%, 50%) rotate(45deg);
    `}
`;

React는 모달을 보여주고 하단의 (+)버튼을 (x)로 바꾸기 위해 &&연산자를 사용합니다. open이 true일 때 모달을 보여주며, 마찬가지로 다른 조건부 명령어인 삼항 연산자 방식 등으로 대체할 수도 있습니다.

 

| 상태 관리

  • Vue - Vuex
import Vue from 'vue'
import Vuex from 'vuex';

Vue.use(Vuex);
export default new Vuex.Store({
  state: {
    todos : [
      {
        id: 1,
        text: 'Vue.js로 Todo List 만들기(1)',
        done: true
      },
      {
        id: 2,
        text: 'Vue.js로 Todo List 만들기(2)',
        done: true
      },
      {
        id: 3,
        text: 'Vue.js로 Todo List 만들기(3)',
        done: false
      },
      {
        id: 4,
        text: 'Vue.js로 Todo List 만들기(4)',
        done: false
      }
    ],
    nextID : 5
  },
  getters: {
    getUndoneTodo: state => {
      return state.todos.filter(todo => !todo.done).length;
    }
  },
  mutations: {
    CREATE_TODO(state, todoItem) {
      state.todos.push(todoItem);
    },

    TOGGLE_TODO(state, todoItem) {
      state.todos = state.todos.map(todo =>
          todo.id === todoItem.id ? { ...todo, done: !todo.done } : todo
        );
    },

    REMOVE_TODO(state, todoItem) {
	    state.todos = state.todos.filter(todo => todo.id !== todoItem.id);
    }
  },
  actions: {
    onSubmit({commit}, todoItem) {
      commit('CREATE_TODO', todoItem)
    },
    onToggle({commit}, todoItem) {
      commit('TOGGLE_TODO', todoItem)
    },
    onRemove({commit}, todoItem) {
      commit('REMOVE_TODO', todoItem)
    }
  }
});

Vuex를 이용해 상태를 관리했습니다. store에 state를 저장해놓고, mutations만이 state를 변경할 수 있습니다. 그리고 actions를 통해 mutatins에 정의된 메소드를 호출하는 방식이었습니다. 이 또한 앞서 살펴본 v-for나 v-if처럼 일정한 패턴이 갖춰져 있고, 거기에 맞춰 작성한다는 느낌을 많이 받았습니다.

 

 

  • React - Context API
import React, { useReducer, createContext, useContext, useRef } from 'react';

const initialTodos = [
  {
    id: 1,
    text: 'React로 Todolist 만들기(1)',
    done: true
  },
  {
    id: 2,
    text: 'React로 Todolist 만들기(2)',
    done: true
  },
  {
    id: 3,
    text: 'React로 Todolist 만들기(3)',
    done: false
  },
  {
    id: 4,
    text: 'React로 Todolist 만들기(4)',
    done: false
  }
];

function todoReducer(state, action) {
  switch (action.type) {
    case 'CREATE':
      return state.concat(action.todo);
    case 'TOGGLE':
      return state.map(todo =>
        todo.id === action.id ? { ...todo, done: !todo.done } : todo
      );
    case 'REMOVE':
      return state.filter(todo => todo.id !== action.id);
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
}

const TodoStateContext = createContext();
const TodoDispatchContext = createContext();
const TodoNextIdContext = createContext();

export function TodoProvider({ children }) {
  const [state, dispatch] = useReducer(todoReducer, initialTodos);
  const nextId = useRef(5);

  return (
  <TodoStateContext.Provider value={state}>
    <TodoDispatchContext.Provider value={dispatch}>
      <TodoNextIdContext.Provider value={nextId}>
      	{children}
      </TodoNextIdContext.Provider>
    </TodoDispatchContext.Provider>
  </TodoStateContext.Provider>
  );
}

export function useTodoState() {
  const context = useContext(TodoStateContext);
  if (!context)
  	throw new Error('Cannot find TodoProvider');
  return context;
}

export function useTodoDispatch() {
  const context = useContext(TodoDispatchContext);
  if (!context)
  	throw new Error('Cannot find TodoProvider');
  return context;
}

export function useTodoNextId() {
  const context = useContext(TodoNextIdContext);
  if (!context)
  	throw new Error('Cannot find TodoProvider');
  return context;
}

Context API를 이용해 상태를 관리했습니다. dispatcher가 액션을 전달하면, reducer가 액션에 따라 state를 변화시킵니다. 위 코드에서는 커스텀 Hook을 구현하여 Hook 내부에서 useContext를 사용하고 있습니다.

 

 

 

최종 선정 : React

  •  자유도

개인적으로 뷰에 대한 첫인상은 자유도가 낮다는 것이었습니다. 상태관리를 위해 사용한 Vuex도 그렇고, v-if나 v-for처럼 정해진 패턴에 맞춰 코드를 작성해야 한다는 특징이 있었습니다. Todo List를 작성하면서 가장 React와의 차이를 크게 느낀 부분이기도 합니다.

React가 JSX를 사용하는 것에 반해 Vue.js는 javascript 사용한다고 하여, 금방 적응할 수 있으리라 예상했지만, 이러한 패턴이 익숙지 않아 오히려 어려움이 있었습니다.

 

이러한 특징은 일관성 있는 코드를 작성할 수 있게 도와준다는 점에서, 오히려 팀프로젝트를 진행했을 때는 장점이 될 수 있을 것 같습니다. 하지만 앞으로 진행할 프로젝트는 개인 프로젝트가 될 예정이기 때문에 Vue를 선택하지 않았습니다.

 

  •  숙련도

Vue의 러닝 커브가 낮다고는 하나, 앞서 말한 것 처럼 Vue의 코드 작성 방식은 거의 패턴으로 고정되어있기 때문에, 적응하는데 시간이 필요할 것 같습니다. 평소에 주로 javascript로 프로젝트를 진행해 왔고, 팀 프로젝트 시에도 React를 활용했기 때문에 당장 Vue로 프로젝트를 진행하는 것은 무리가 있고, 아직 숙련도가 너무 낮다고 생각됩니다.

 

 

  •  넓은 커뮤니티

Google Trends와 Stackoverflow의 질문 수

출처: Radity

 

React는 가장 인기있으며, 넓은 커뮤니티를 보유하고 있습니다. Todo List를 작성하면서, 여러 자료를 참고하였는데, 이때에도 React의 자료가 훨씬 많은 것을 느낄 수 있었습니다.