[React] Vue와의 비교!

한강섭·2025년 7월 12일
1
post-thumbnail

이번 프로젝트를 React로 하게 될 것 같아서 Vue의 개념과 매핑되는 React 개념을 정리해보겠습니다.


1) 폴더 구조

Vue3

src/
├── components/
│   ├── Counter.vue
│   	├── <template>  (HTML)
│   	├── <script>    (JavaScript)
│		└── <style>     (CSS)
└── App.vue

React

src/
├── components/
│   ├── Counter.jsx         // 컴포넌트 로직
│   └── Counter.module.css  // 스타일
└── App.jsx

Vue3는 SFC (Single File Component) 라서 한 파일에 HTML, JavaScript, CSS 가 다 들어있는 반면, React는 css는 따로 분리되는 모습

.css 앞에 붙어있는 .module은 Vue의 scoped 역할을 해서 해당 컴포넌트에만 영향을 줄 수 있도록 한다.

파일 예시

Counter.vue

<template>
  <div class="counter">
    <h2>{{ title }}</h2>
    <p class="count">{{ count }}</p>
    <button @click="increment" class="btn">증가</button>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const count = ref(0)
const title = '카운터'

const increment = () => {
  count.value++
}
</script>

<style scoped>
.counter {
  padding: 20px;
  border: 1px solid #ccc;
}

.count {
  font-size: 24px;
  font-weight: bold;
}

.btn {
  background: blue;
  color: white;
  padding: 10px;
}
</style>

Counter.jsx

import React, { useState } from 'react';
import styles from './Counter.module.css';

function Counter() {
  const [count, setCount] = useState(0);
  const title = '카운터';

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div className={styles.counter}>
      <h2>{title}</h2>
      <p className={styles.count}>{count}</p>
      <button onClick={increment} className={styles.btn}>
        증가
      </button>
    </div>
  );
}

export default Counter;

Counter.module.css

.counter {
  padding: 20px;
  border: 1px solid #ccc;
}

.count {
  font-size: 24px;
  font-weight: bold;
}

.btn {
  background: blue;
  color: white;
  padding: 10px;
}

2) 상태관리

ref vs useState

// Vue 3
const count = ref(0)
count.value = 10  // 값 변경

// React
const [count, setCount] = useState(0)
setCount(10)  // 값 변경

reactive vs useState

// Vue 3
const user = reactive({
  name: 'John',
  age: 25
})
user.name = 'Jane'  // 직접 변경 가능

// React
const [user, setUser] = useState({
  name: 'John',
  age: 25
})
setUser({...user, name: 'Jane'})  // 불변성 유지

computed vs useMemo

// Vue 3
const count = ref(0)
const doubleCount = computed(() => count.value * 2)

// React
const [count, setCount] = useState(0)
const doubleCount = useMemo(() => count * 2, [count])

watch vs useEffect

// Vue 3
watch(count, (newVal, oldVal) => {
  console.log(`${oldVal} -> ${newVal}`)
})

// React
useEffect(() => {
  console.log('count changed:', count)
}, [count])

3) 라이프 사이클 및 사이드 이펙트

컴포넌트 마운트

// Vue 3
onMounted(() => {
  console.log('컴포넌트가 마운트됨')
})

// React
useEffect(() => {
  console.log('컴포넌트가 마운트됨')
}, [])  // 빈 배열로 한 번만 실행

컴포넌트 언마운트

// Vue 3
onUnmounted(() => {
  console.log('컴포넌트가 언마운트됨')
})

// React
useEffect(() => {
  return () => {
    console.log('컴포넌트가 언마운트됨')
  }
}, [])  // cleanup 함수 반환

자동 의존성 감시

// Vue 3
watchEffect(() => {
  console.log('count:', count.value)
  console.log('name:', name.value)
  // count나 name이 변경되면 자동 실행
})

// React
useEffect(() => {
  console.log('count:', count)
  console.log('name:', name)
}, [count, name])  // 의존성 배열 명시 필요

4) 조건부 렌더링과 리스트

Vue 3 버전

<template>
  <div>
    <input v-model="newTodo" @keyup.enter="addTodo" placeholder="할 일 입력">
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        <span :class="{ completed: todo.done }" @click="toggleTodo(todo.id)">
          {{ todo.text }}
        </span>
        <button @click="removeTodo(todo.id)">삭제</button>
      </li>
    </ul>
    <p v-if="todos.length === 0">할 일이 없습니다.</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const newTodo = ref('')
const todos = ref([])

const addTodo = () => {
  if (newTodo.value.trim()) {
    todos.value.push({
      id: Date.now(),
      text: newTodo.value,
      done: false
    })
    newTodo.value = ''
  }
}

const toggleTodo = (id) => {
  const todo = todos.value.find(t => t.id === id)
  if (todo) todo.done = !todo.done
}

const removeTodo = (id) => {
  todos.value = todos.value.filter(t => t.id !== id)
}
</script>

React 버전

import React, { useState } from 'react';

function TodoList() {
  const [newTodo, setNewTodo] = useState('');
  const [todos, setTodos] = useState([]);

  const addTodo = () => {
    if (newTodo.trim()) {
      setTodos([...todos, {
        id: Date.now(),
        text: newTodo,
        done: false
      }]);
      setNewTodo('');
    }
  };

  const toggleTodo = (id) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, done: !todo.done } : todo
    ));
  };

  const removeTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  const handleKeyPress = (e) => {
    if (e.key === 'Enter') {
      addTodo();
    }
  };

  return (
    <div>
      <input 
        value={newTodo} 
        onChange={(e) => setNewTodo(e.target.value)}
        onKeyPress={handleKeyPress}
        placeholder="할 일 입력"
      />
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <span 
              className={todo.done ? 'completed' : ''}
              onClick={() => toggleTodo(todo.id)}
            >
              {todo.text}
            </span>
            <button onClick={() => removeTodo(todo.id)}>삭제</button>
          </li>
        ))}
      </ul>
      {todos.length === 0 && <p>할 일이 없습니다.</p>}
    </div>
  );
}

export default TodoList;

불변성

vue, react 둘 다 javascript의 프레임워크로써 비슷하게 작동하지만 하나 큰 차이점이 있는데, 바로 Vue는 가변성이고, React는 불변성이란 것이다.

그래서 react에서는 set으로 새로운 객체를 생성해서 덮어쓰기 해준다.

import React, { useState } from 'react'

function App() {
  const [user, setUser] = useState({
    name: '홍길동',
    age: 25
  })
  
  const [items, setItems] = useState(['사과', '바나나'])

  const updateData = () => {
    // 직접 수정하면 안 됨
    // user.name = '김철수'  // React가 변화 감지 못함
    // items.push('오렌지')  // React가 변화 감지 못함
    
    // 새로운 객체/배열 생성
    setUser({
      ...user,  // 기존 데이터 복사
      name: '김철수',  // 새로운 값
      age: 30
    })
    
    setItems([
      ...items,  // 기존 배열 복사
      '오렌지'   // 새로운 요소 추가
    ])
  }

  return (
    <div>
      <p>이름: {user.name}</p>
      <p>나이: {user.age}</p>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  )
}

그래서 Vue가 조금 직관적이고 쉽지만 예측하기 어렵고 디버깅이 어려울 수 있지만, React는 반대로 살짝 복잡하지만 디버깅이 용이하다.

profile
기록하고 공유하는 개발자

6개의 댓글

comment-user-thumbnail
2025년 7월 12일

대세는 리액트인가요

1개의 답글
comment-user-thumbnail
2025년 7월 13일

캬 프론트의 신..

1개의 답글
comment-user-thumbnail
2025년 7월 14일

vue랑 react가 친해보여서 기분이 좋아요

1개의 답글