💚
프로그레시브 프레임워크

Vue 완전 가이드

👥 방문자 수

Evan You가 만든 Vue 3를 체계적으로 학습합니다. Composition API부터 Pinia 상태 관리, Vue Router 라우팅까지 실전에서 즉시 활용 가능한 핵심 기술들을 완벽하게 정리했습니다.

Vue 3 Composition API Pinia Vue Router

1. Vue란?

Vue는 사용자 인터페이스를 만들기 위한 진보적인 JavaScript 프레임워크입니다. 선언적 렌더링과 반응성 시스템을 통해 복잡한 UI를 효율적으로 구축할 수 있도록 돕습니다.

2. 개발 환경 구축 (Node.js & Vite & VS Code)

Vue 3를 시작하기 위해 공식 스캐폴딩 도구인 create-vue와 초고속 프론트엔드 빌드 툴 Vite(바이트)를 활용해 최고 효율의 개발 환경을 구축해 보겠습니다.

2.1 사전 준비: Node.js 설치

Vue 3 프로젝트 구동 및 CLI 도구 활용을 위해서는 npm이 포함된 Node.js 환경이 로컬 기기에 필수적입니다. 미설치 상태라면 JavaScript 가이드의 Node.js 설치 세션을 먼저 진행해 주십시오.

2.2 create-vue를 활용한 프로젝트 생성

  1. 프로젝트 생성 실행: 원하는 작업 폴더로 터미널을 열고 아래 명령어를 작성합니다.
    npm create vue@latestbash
  2. 설정 대화 상자 세팅:

    명령어를 실행하면 대화형 프롬프트가 실행됩니다. 아래는 가장 표준적으로 권장되는 선택 조건입니다.

    ✔ Project name: … my-vue-app
    ✔ Add TypeScript? … Yes / No (기호에 맞게 선택)
    ✔ Add JSX Support? … No
    ✔ Add Vue Router for Single Page Application development? … Yes (페이지 라우팅 시 필수)
    ✔ Add Pinia for state management? … Yes (중앙 전역 상태 관리 툴)
    ✔ Add Vitest for Unit Testing? … No
    ✔ Add an End-to-End Testing Solution? … No
    ✔ Add ESLint for code quality? … Yes
    ✔ Add Prettier for code formatting? … Yesbash
  3. 의존성 설치 및 로컬 서버 활성화:
    cd my-vue-app
    npm install
    npm run devbash

    (개발 서버 구동 후 브라우저에서 http://localhost:5173으로 접속하면 멋진 Vue 3 기본 랜딩 화면을 만나볼 수 있습니다.)

2.3 VS Code 최적의 확장 도구 및 디버그 도구 세팅

  1. Vue - Official 확장 설치 (필수):

    VS Code 마켓플레이스(Ctrl + Shift + X)에서 Vue - Official (구 Volar) 공식 확장을 찾아 반드시 설치합니다. Vue 3의 싱글 파일 컴포넌트(.vue) 구조 내부의 HTML/CSS/JS에 최적화된 문법 하이라이팅, 자동 완성 및 TypeScript 분석 기능을 활성화해 줍니다.

    ※ 과거 Vue 2에 쓰이던 Vetur 확장은 충돌 문제를 야기할 수 있으므로 Vue 3 환경에서는 비활성화하는 것을 권장합니다.

  2. Vue Devtools 설치 (브라우저 확장):

    Chrome 또는 Edge 브라우저용 Vue.js devtools 확장을 다운로드해 사용하면, F12 개발자 도구 탭에서 컴포넌트 트리, Pinia 스토어 상태 변경 기록, 컴포넌트 간 전달 이벤트 내역을 실시간으로 추적·디버깅하여 생산성을 극적으로 끌어올려 줍니다.

3. 템플릿 문법 (Template Syntax)

Vue는 선언적으로 렌더링할 수 있는 HTML 기반 템플릿 문법을 지원합니다. 이중 중괄호({{ }}) 콧수염 표기법을 활용해 텍스트 동적 바인딩을 수행합니다.

<template>
  <h1>{{ message }}</h1>
</template>

<script setup>
const message = 'Hello Vue 3!';
</script>vue

4. 반응성 시스템 (Reactivity - ref & reactive)

Vue 3는 refreactive를 통해 데이터가 변경될 때 컴포넌트 화면을 즉시 자동 리렌더링하도록 돕는 정교한 반응성 시스템을 내장하고 있습니다.

<script setup>
import { ref, reactive } from 'vue';

// 1. ref: 모든 원시 자료형 및 객체에 사용 (value 속성을 통해 접근)
const count = ref(0);
const increment = () => count.value++;

// 2. reactive: 객체 전용 반응성 바인딩 (.value 없이 직접 수정)
const user = reactive({
    name: '홍길동',
    age: 30
});
</script>

<template>
  <button @click="increment">클릭수: {{ count }}</button>
  <p>이름: {{ user.name }}</p>
</template>vue

5. 컴퓨티드와 감시자 (computed & watch)

computed는 반응성 데이터를 기반으로 하는 계산된 속성을 선언하며 캐싱 처리가 수행됩니다. watch는 데이터의 변화를 직접 감지해 비동기 작업 등의 사이드 이펙트를 실행합니다.

<script setup>
import { ref, computed, watch } from 'vue';

const price = ref(1000);
const quantity = ref(3);

// price나 quantity가 바뀔 때만 캐싱 연산 수행
const total = computed(() => price.value * quantity.value);

// price의 변화를 정밀 트래킹
watch(price, (newValue, oldValue) => {
    console.log(`가격 변동 감지: ${oldValue}원 -> ${newValue}원`);
});
</script>vue

6. 디렉티브 (Directives)

디렉티브는 템플릿 안에서 DOM 동작을 손쉽게 수행하기 위해 제공되는 Vue 고유의 특수 속성 접두사(v-)입니다.

<script setup>
import { ref } from 'vue';
const isVisible = ref(true);
const textInput = ref('');
const list = ref(['HTML', 'CSS', 'JavaScript']);
</script>

<template>
  {/* v-if: 조건부 DOM 추가/삭제 */}
  <p v-if="isVisible">보이는 상태입니다.</p>

  {/* v-for: 리스트 순회 */}
  <ul>
    <li v-for="(item, idx) in list" :key="idx">{{ item }}</li>
  </ul>

  {/* v-model: 양방향 데이터 바인딩 */}
  <input v-model="textInput" placeholder="입력하세요" />
  <p>입력값: {{ textInput }}</p>
</template>vue

7. 이벤트 처리 (Event Handling)

이벤트 리스너는 v-on 디렉티브나 그 단축어인 @ 기호를 사용해 손쉽게 DOM 이벤트를 감청하고 반응 메서드를 등록합니다.

<script setup>
const sayHello = (msg) => {
    alert(`안녕하세요: ${msg}`);
};
</script>

<template>
  {/* 메서드 매핑 및 인자 전송 */}
  <button @click="sayHello('TestForge')">인사하기</button>
</template>vue

8. 컴포넌트 기초 (Components - Props & Emits)

싱글 파일 컴포넌트(SFC)를 자식으로 가져와 조립할 때, 데이터는 defineProps로 주입받고, 역방향 이벤트 알림은 defineEmits를 구성합니다.

<!-- 자식 컴포넌트 (Child.vue) -->
<script setup>
// 1. props 정의
defineProps({
    title: String
});
// 2. emits 정의
const emit = defineEmits(['notifyParent']);
</script>

<template>
  <div class="child">
    <h3>제목: {{ title }}</h3>
    <button @click="emit('notifyParent', '클릭됨')">부모에게 신호 보내기</button>
  </div>
</template>vue

9. Composition API (script setup)

Vue 3의 핵심. <script setup>으로 관련 로직을 함수 단위로 묶어 격리·재사용합니다.

<script setup lang="ts">
import { ref, reactive, toRefs, onMounted, onUnmounted, provide } from 'vue';

// ── 생명주기 훅 ────────────────────────────────────
onMounted(()   => console.log('DOM 마운트 완료'));
onUnmounted(() => console.log('컴포넌트 소멸'));

// ── reactive vs ref ────────────────────────────────
// ref: 단순 값 (항상 .value로 접근)
const count = ref(0);

// reactive: 객체 전용 (구조 분해 시 반응성 소실 주의)
const state = reactive({ name: '', email: '', loading: false });

// toRefs: 구조 분해해도 반응성 유지
const { name, loading } = toRefs(state);

// ── 비동기 데이터 패칭 ─────────────────────────────
const users = ref([]);
onMounted(async () => {
    state.loading = true;
    try {
        const res = await fetch('/api/users');
        users.value = await res.json();
    } finally {
        state.loading = false;
    }
});

// ── provide / inject (부모→자식 전달, props drilling 없이) ──
const theme = ref('dark');
provide('theme', theme);       // 하위 모든 컴포넌트에서 inject('theme')으로 받음
</script>

<template>
  <div>
    <p v-if="loading">로딩 중...</p>
    <ul v-else>
      <li v-for="user in users" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>vue

10. Composables (Custom Composables)

Composables는 React 커스텀 훅과 동일한 개념입니다. 반응성 로직을 독립 파일로 분리해 재사용합니다.

// composables/useFetch.ts
import { ref, watchEffect } from 'vue';

export function useFetch<T>(url: string | (() => string)) {
    const data    = ref<T | null>(null);
    const loading = ref(true);
    const error   = ref<Error | null>(null);

    watchEffect(async (onCleanup) => {
        const resolvedUrl = typeof url === 'function' ? url() : url;
        loading.value = true;
        error.value   = null;

        let cancelled = false;
        onCleanup(() => { cancelled = true; }); // URL 변경 시 이전 요청 취소

        try {
            const res = await fetch(resolvedUrl);
            if (!res.ok) throw new Error(`HTTP ${res.status}`);
            const json = await res.json();
            if (!cancelled) data.value = json;
        } catch (e) {
            if (!cancelled) error.value = e as Error;
        } finally {
            if (!cancelled) loading.value = false;
        }
    });

    return { data, loading, error };
}

// composables/useLocalStorage.ts
import { ref, watch } from 'vue';

export function useLocalStorage<T>(key: string, initial: T) {
    const stored = localStorage.getItem(key);
    const value  = ref<T>(stored ? JSON.parse(stored) : initial);

    watch(value, (v) => localStorage.setItem(key, JSON.stringify(v)), { deep: true });

    return value;
}

// ── 사용 예시 ─────────────────────────────────────
// UserList.vue
<script setup>
const userId   = ref(1);
const { data: user, loading } = useFetch(() => `/api/users/${userId.value}`);
const theme    = useLocalStorage('theme', 'light');
</script>javascript

11. Vue Router

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import { useAuthStore } from '@/stores/auth';

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: () => import('@/layouts/DefaultLayout.vue'),  // 레이아웃
      children: [
        { path: '',       name: 'home',   component: () => import('@/views/HomeView.vue') },
        { path: 'blog',   name: 'blog',   component: () => import('@/views/BlogView.vue') },
        { path: 'blog/:id', name: 'post', component: () => import('@/views/PostView.vue'),
          props: true },  // params를 props로 전달
      ],
    },
    {
      path: '/admin',
      component: () => import('@/layouts/AdminLayout.vue'),
      meta: { requiresAuth: true },  // 네비게이션 가드용 메타
      children: [
        { path: '', component: () => import('@/views/admin/Dashboard.vue') },
      ],
    },
    { path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFound.vue') },
  ],
});

// ── 네비게이션 가드 ────────────────────────────────
router.beforeEach((to, _from) => {
    const auth = useAuthStore();
    if (to.meta.requiresAuth && !auth.isLoggedIn) {
        return { name: 'login', query: { redirect: to.fullPath } };
    }
});

export default router;javascript
<!-- App.vue -->
<script setup>
import { useRoute, useRouter } from 'vue-router';

const route  = useRoute();   // 현재 라우트 정보
const router = useRouter();  // 프로그래매틱 이동

// 파라미터 접근
const postId = route.params.id;
const page   = route.query.page;

// 이동
const goBack = () => router.back();
const goHome = () => router.push({ name: 'home' });
</script>

<template>
  <RouterLink :to="{ name: 'post', params: { id: 1 } }">글 보기</RouterLink>
  <RouterView />
</template>vue

12. Pinia

Vue 3 공식 상태 관리 라이브러리입니다. Composition API 스타일로 직관적이며 TypeScript 친화적입니다.

// stores/auth.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

export const useAuthStore = defineStore('auth', () => {
    // ── state ──────────────────────────────────────
    const user    = ref<User | null>(null);
    const token   = ref<string | null>(localStorage.getItem('token'));

    // ── getters (computed) ─────────────────────────
    const isLoggedIn  = computed(() => !!token.value);
    const displayName = computed(() => user.value?.name ?? '게스트');

    // ── actions ────────────────────────────────────
    async function login(email: string, password: string) {
        const res = await fetch('/api/auth/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ email, password }),
        });
        if (!res.ok) throw new Error('로그인 실패');
        const data = await res.json();
        token.value = data.token;
        user.value  = data.user;
        localStorage.setItem('token', data.token);
    }

    function logout() {
        token.value = null;
        user.value  = null;
        localStorage.removeItem('token');
    }

    return { user, token, isLoggedIn, displayName, login, logout };
});

// ── 컴포넌트에서 사용 ──────────────────────────────
// <script setup>
// const auth = useAuthStore();
// auth.login(email, password);
// console.log(auth.displayName);
// </script>typescript

13. 다음 단계 (Next Steps)

Vue 3의 강력한 Composition API, 싱글 파일 컴포넌트(SFC), Pinia 상태 조작을 완전히 습득하셨다면, 대형 어플리케이션 구축을 위해 정적 파일 번들러 최적화 및 Nuxt.js 프레임워크 연구로 전개하세요!

🚀
추천 학습 경로:
Vue 3 SFC 기본 → Composition API 마스터 → Vue Router/Pinia 결합 실습 → TypeScript 적용 → Nuxt.js(SSR) 학습

연계 가이드: 💡 Nuxt.js 완전 가이드 · TypeScript 가이드 · Node.js 가이드