React vs Vue 3 비교
| React | Vue 3 |
|---|---|
useState | ref() / reactive() |
useEffect | onMounted() / watch() |
@tanstack/react-query | @tanstack/vue-query |
react-router-dom | vue-router |
| JSX | SFC Template (.vue) |
children props | <slot /> |
프로젝트 구조
폴더 구조
examples/b2b-admin/web-vue/
├── src/
│ ├── main.ts # 앱 진입점
│ ├── App.vue # 루트 컴포넌트
│ ├── api/
│ │ └── client.ts # API 클라이언트 (React와 동일)
│ ├── types/
│ │ └── api.ts # 타입 정의 (React와 동일)
│ ├── composables/ # Vue Hooks (React의 hooks/)
│ │ ├── useHealth.ts
│ │ └── useProjects.ts
│ ├── router/
│ │ └── index.ts # Vue Router 설정
│ ├── components/
│ │ ├── HealthStatus.vue
│ │ ├── AppLayout.vue
│ │ ├── ProjectForm.vue
│ │ └── ProjectCard.vue
│ └── pages/
│ ├── ProjectsPage.vue
│ └── ProjectDetailPage.vue
├── package.json
├── vite.config.ts
└── tailwind.config.jsComposition API 예제
상태 관리 (ref)
React
React
const [name, setName] = useState('');
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>Vue 3
Vue
const name = ref('');
<input v-model="name" />Vue Query Composable
composables/useProjects.ts
// composables/useProjects.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query';
import { computed, type Ref } from 'vue';
import { apiClient } from '../api/client';
export function useProjects(page: Ref<number> = ref(1)) {
return useQuery({
queryKey: computed(() => ['projects', page.value]),
queryFn: () =>
apiClient.get(`/api/v1/projects?page=${page.value}`),
});
}
export function useCreateProject() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data) =>
apiClient.post('/api/v1/projects', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['projects'] });
},
});
}Vue 컴포넌트 예제
components/ProjectForm.vue
<script setup lang="ts">
import { ref } from 'vue';
import { useCreateProject } from '../composables/useProjects';
const name = ref('');
const description = ref('');
const { mutate: createProject, isPending } = useCreateProject();
const handleSubmit = () => {
if (!name.value.trim()) return;
createProject(
{ name: name.value.trim(), description: description.value.trim() },
{
onSuccess: () => {
name.value = '';
description.value = '';
},
}
);
};
</script>
<template>
<form @submit.prevent="handleSubmit" class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">
프로젝트 이름
</label>
<input
v-model="name"
type="text"
class="w-full px-3 py-2 border border-gray-300 rounded-lg"
placeholder="프로젝트 이름 입력"
required
/>
</div>
<button
type="submit"
:disabled="isPending"
class="w-full bg-blue-600 text-white py-2 rounded-lg"
>
{{ isPending ? '생성 중...' : '프로젝트 생성' }}
</button>
</form>
</template>로컬 실행
터미널
# 1. 백엔드 실행 (Hono 권장)
cd examples/b2b-admin/api-hono
npm install
npm run dev
# 2. Vue 프론트엔드 실행 (새 터미널)
cd examples/b2b-admin/web-vue
npm install
npm run dev
# 브라우저에서 http://localhost:5174 접속Cloudflare Pages 배포
React와 동일하게 Cloudflare Pages에 배포할 수 있습니다.
public/_redirects 파일이 이미 포함되어 있어
SPA 라우팅 문제가 해결됩니다.
| Framework preset | Vite |
| Build command | npm run build |
| Build output directory | dist |
| Root directory | examples/b2b-admin/web-vue |
핵심 포인트
- API 클라이언트와 타입 정의는 React와 100% 동일
- TanStack Query는 React/Vue 모두 지원
- 프레임워크가 달라도 동일한 백엔드 API 사용 가능