1. Vue.js란?
Vue.js
- 사용자 인터페이스를 만들기 위한 프로그레시브 JavaScript 프레임워크
- 컴포넌트 기반으로 동작하며, 반응형 데이터 바인딩과 가상 DOM을 활용하여 효율적인 렌더링을 제공
DOM(Document Object Model): HTML 문서를 JavaScript가 이해하고 조작할 수 있도록 트리 구조로 표현한 것
예를 들어 이런 HTML이 있다면:
<div id="app">
<h1>제목</h1>
<p>내용</p>
</div>
브라우저는 이걸 트리 구조의 객체로 만듦. JavaScript로 document.getElementById('app')처럼 접근해서 내용을 바꾸거나 스타일을 변경할 수 있음.
- DOM 조작의 문제는 느리다는 것. 특히 여러 번 수정하면 브라우저가 매번 화면을 다시 그려야 해서 성능이 떨어짐.
- 그래서 Vue 같은 프레임워크들이 가상 DOM을 써서 변경사항을 모아뒀다가 한 번에 실제 DOM에 반영하는 것. 개발자는 DOM을 직접 건드리지 않고 데이터만 바꾸면 됨.
반응형 데이터 바인딩: 데이터가 변경되면 자동으로 화면(View)도 업데이트되는 것
- ex) data에 count: 0이 있고 화면에 {{ count }}로 표시했다면, JavaScript에서 this.count = 5로 값을 바꾸는 순간 화면도 자동으로 5로 바뀜. 개발자가 직접 DOM을 조작할 필요가 없음.
가상 DOM은 실제 DOM의 복사본을 메모리에 가지고 있는 개념
- 데이터가 변경되면 Vue는 먼저 가상 DOM에서 변경사항을 계산하고, 실제 DOM과 비교해서 바뀐 부분만 실제 DOM에 반영함. 실제 DOM 조작은 비용이 크기 때문에 이렇게 하면 성능이 훨씬 좋아짐.
- ex) 100개 항목 중 1개만 바뀌었다면, 전체를 다시 그리지 않고 그 1개만 업데이트
※ React
리액트는 단방향이라서 화면에서 입력값이 바뀌어도 자동으로 데이터가 바뀌지 않음.
const [username, setUsername] = useState('');
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
- value={username}: 데이터 → 화면 (단방향)
- onChange 이벤트로 입력값이 바뀔 때마다 setUsername을 직접 호출해서 데이터를 업데이트해야 함
Vue의 v-model은 이 과정을 자동으로 해주는 거고, 리액트는 개발자가 명시적으로 이벤트 핸들러를 작성해야 함.
결과적으로는 똑같이 동작하지만, Vue는 편의 기능으로 양방향처럼 보이게 해주고, 리액트는 모든 데이터 흐름을 명시적으로 관리하는 차이임.
2. Vue 프로젝트 구조
Vue CLI로 생성한 프로젝트의 기본 구조를 이해하는 것이 중요함.
src/
├── main.js # 애플리케이션 진입점
├── App.vue # 최상위 컴포넌트
├── components/ # 재사용 가능한 컴포넌트들
├── views/ # 페이지 단위 컴포넌트들
├── router/ # 라우팅 설정
├── store/ # Vuex 상태 관리
└── assets/ # 정적 리소스
1. main.js (시작점)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
- 앱이 시작되는 곳임
- router(라우팅), store(상태관리)를 Vue 인스턴스에 주입함
- App.vue를 렌더링해서 index.html의 <div id="app">에 마운트함
- 템플릿(HTML)이 필요 없어서 .js 파일임
2. App.vue (최상위 컴포넌트)
<template>
<div id="app">
<Header />
<router-view /> <!-- 라우팅된 페이지가 여기 표시됨 -->
<Footer />
</div>
</template>
- 전체 앱의 레이아웃을 담당함
- 공통 컴포넌트(헤더, 푸터 등)를 배치함
- <router-view>에 URL에 따라 다른 페이지가 렌더링됨
- <template>, <script>, <style>이 모두 필요해서 .vue 파일임
3. router/ (라우팅 설정)
const routes = [
{ path: '/', component: Home }, // views/Home.vue
{ path: '/about', component: About } // views/About.vue
]
- URL 경로와 페이지 컴포넌트를 매핑함
- /로 접속하면 Home.vue를, /about로 접속하면 About.vue를 <router-view>에 보여줌
4. views/ (페이지 컴포넌트)
<!-- views/Home.vue -->
<template>
<div>
<ProductList /> <!-- components/에서 가져옴 -->
<UserCard />
</div>
</template>
- 각 URL 경로에 대응하는 페이지임
- 여러 개의 재사용 컴포넌트들을 조합해서 페이지를 구성함
5. components/ (재사용 컴포넌트)
<!-- components/ProductList.vue -->
<template>
<div v-for="product in products" :key="product.id">
{{ product.name }}
</div>
</template>
- 버튼, 카드, 리스트 등 여러 곳에서 재사용되는 컴포넌트들임
- views/에서 import해서 사용함
6. store/ (Vuex 상태 관리)
const store = new Vuex.Store({
state: { user: null },
mutations: { setUser(state, user) { state.user = user } }
})
- 여러 컴포넌트가 공유하는 전역 데이터를 관리함
- 예: 로그인한 사용자 정보, 장바구니 데이터 등
- 어떤 컴포넌트에서든 this.$store.state.user로 접근 가능함
7. assets/ (정적 리소스)
- 이미지, 폰트, CSS 파일 등을 저장함
- 컴포넌트에서 import logo from '@/assets/logo.png'처럼 사용함
전체 흐름
- 브라우저가 index.html 로드 → main.js 실행
- main.js가 App.vue 마운트 + router, store 주입
- 사용자가 /about 접속 → router가 About.vue를 <router-view>에 렌더링
- About.vue가 components/Button.vue 같은 재사용 컴포넌트 사용
- 필요하면 store에서 전역 데이터 가져오거나 수정함
3. MVVM 패턴
Vue.js는 MVVM(Model-View-ViewModel) 패턴을 따름
- Model: 데이터와 비즈니스 로직
- View: 사용자에게 보이는 화면
- ViewModel: View와 Model을 연결하는 중간 계층으로, Vue 인스턴스가 이 역할을 수행
데이터 바인딩을 통해 Model의 변경사항이 자동으로 View에 반영되며, 반대로 View에서의 사용자 입력도 Model에 자동으로 업데이트됨
4. SFC (Single File Component)
Vue 컴포넌트는 .vue 확장자를 가진 단일 파일로 작성됨. 하나의 파일에 템플릿, 스크립트, 스타일을 모두 포함함.
<template>
<div>{{ message }}</div>
</template>
<script>
export default {
data() {
return { message: 'Hello' }
}
}
</script>
<style scoped>
div { color: blue; }
</style>
- template: HTML 마크업
- script: JavaScript 로직
- style: CSS 스타일 (scoped 속성으로 해당 컴포넌트에만 적용 가능)
5. Vue 인스턴스 옵션
- 인스턴스 옵션 = 재료와 조리법 (JavaScript), 데이터와 로직
- 디렉티브 = 완성된 요리를 그릇에 담기 (HTML), 화면 구조
→ 둘이 같이 작동해야 Vue가 제대로 동작함!
Vue 컴포넌트를 정의할 때 사용하는 주요 옵션들
data
컴포넌트의 반응형 데이터를 정의함. 함수로 작성해야 각 인스턴스가 독립적인 데이터를 가짐.
data() {
return { count: 0 }
}
methods
이벤트 핸들러나 일반 메서드를 정의
methods: {
increment() { this.count++ }
}
computed
계산된 속성으로, 의존하는 데이터가 변경될 때만 재계산됨. 캐싱이 되어 성능상 유리함.
computed: {
doubleCount() { return this.count * 2 }
}
watch
특정 데이터의 변화를 감지하여 비동기 작업이나 복잡한 로직을 수행
watch: {
count(newVal, oldVal) { console.log('changed') }
}
| 구분 | computed | watch |
| 용도 | 값을 계산해서 반환 | 값 변화 감지 후 로직 실행 |
| 캐싱 | O (의존 값 안 바뀌면 재계산 X) | X |
| 반환값 | 필수 | 선택 |
| 비동기 | X | O |
| 사용 예 | fullName = firstName + lastName | API 호출, 로그 기록 |
| 사용 시점 | 템플릿에서 바로 사용 | 특정 값 변경 시 부수 효과 처리 |
| 성격 | 선언적 (결과 중심) | 명령적 (행위 중심) |
Vue 3의 Composition API
ref (모든 값)
import { ref } from 'vue'
const count = ref(0)
const name = ref('홍길동')
const user = ref({ id: 1, name: '김철수' })
// 접근 시 .value 필요
console.log(count.value) // 0
count.value++
reactive (객체, 배열)
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: { id: 1, name: '김철수' }
})
// .value 없이 바로 접근
console.log(state.count) // 0
state.count++
차이점
- ref: 원시값(숫자, 문자열), 객체 모두 가능. .value로 접근
- reactive: 객체, 배열만 가능. .value 없이 바로 접근
<template>
<!-- ref는 template에서 .value 생략 가능 -->
<p>{{ count }}</p>
<!-- reactive는 그냥 사용 -->
<p>{{ state.count }}</p>
</template>
<script setup>
import { ref, reactive } from 'vue'
const count = ref(0) // template에서 {{ count }}
const state = reactive({ count: 0 }) // template에서 {{ state.count }}
</script>
JSON 변환 시 주의
// ref나 reactive를 JSON으로 바꿀 때
const user = ref({ id: 1, name: '홍길동' })
// ❌ 잘못된 방법
JSON.stringify(user) // 반응형 시스템까지 포함되어 에러 발생
// ✅ 올바른 방법 - ref인 경우
JSON.stringify(user.value)
// ✅ 올바른 방법 - reactive인 경우
import { toRaw } from 'vue'
JSON.stringify(toRaw(state))
반드시 ref는 .value, reactive는 toRaw()를 거쳐야 안전하게 처리됨.
6. 주요 디렉티브
v-bind (축약: :)
HTML 속성에 데이터를 바인딩
<img :src="imagePath">
v-model
양방향 데이터 바인딩을 제공. 주로 폼 입력 요소에 사용됨.
<input v-model="username">
v-if / v-show
조건부 렌더링을 처리함. v-if는 조건에 따라 DOM을 추가/제거하고, v-show는 CSS display 속성만 변경.
v-for
배열이나 객체를 순회하며 반복 렌더링. 반드시 :key를 함께 사용해야 함.
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
v-on (축약: @)
이벤트 리스너를 등록.
<button @click="handleClick">클릭</button>
7. 컴포넌트 통신
Props (부모 → 자식)
부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달
// 자식 컴포넌트
props: ['title']
<!-- 부모 컴포넌트 -->
<child-component :title="parentTitle"></child-component>
Events (자식 → 부모)
자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달
// 자식: this.$emit('update', newValue)
<!-- 부모 -->
<child-component @update="handleUpdate"></child-component>
Slots
부모 컴포넌트에서 자식 컴포넌트로 템플릿 내용을 전달
<!-- 자식 컴포넌트 -->
<div><slot></slot></div>
<!-- 부모 컴포넌트 -->
<child-component>여기 내용이 slot에 들어간다</child-component>
8. 라이프사이클 훅
생성 단계:
- beforeCreate: 인스턴스 초기화 직후
- created: 데이터 초기화 완료, DOM은 아직 없음 (API 호출하기 좋은 시점)
마운트 단계:
- beforeMount: DOM 마운트 직전
- mounted: DOM에 마운트 완료 (DOM 접근 가능)
업데이트 단계:
- beforeUpdate: 데이터 변경 후 DOM 업데이트 전
- updated: DOM 업데이트 완료
소멸 단계:
- beforeDestroy: 인스턴스 소멸 직전 (이벤트 리스너 제거 등)
- destroyed: 인스턴스 완전히 소멸
9. Vue Router
SPA 개발을 위한 공식 라우팅 라이브러리
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/user/:id', component: User } // 동적 라우트
]
router-link와 router-view
<router-link to="/about">About</router-link> <!-- 링크 -->
<router-view></router-view> <!-- 컴포넌트 렌더링 위치 -->
프로그래밍 방식 네비게이션
this.$router.push('/home')
10. Vuex
여러 컴포넌트가 공유하는 상태를 중앙에서 관리
ref, reactive를 통해 컴포넌트 간 상태 공유 더 유연하고 간단하게 처리 가능하므로 Vuex 사용 잘 안 함.
const store = new Vuex.Store({
state: { count: 0 },
getters: { doubleCount: state => state.count * 2 },
mutations: {
increment(state) { state.count++ }
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => commit('increment'), 1000)
}
}
})
사용법:
- State 접근: this.$store.state.count
- Mutations 호출: this.$store.commit('increment')
- Actions 호출: this.$store.dispatch('incrementAsync')
11. Axios를 통한 HTTP 통신
Vue에서는 주로 Axios 라이브러리를 사용해 API 통신
axios.get('/api/users')
.then(response => { this.users = response.data })
보통 created() 또는 mounted() 훅에서 API를 호출
'SKALA' 카테고리의 다른 글
| Front-framework: Vue.js - Stock Market (0) | 2026.02.09 |
|---|---|
| HTML, CSS, JavaScript (0) | 2026.02.05 |
| 생성형 AI 기초 및 Prompt Engineering (0) | 2026.02.03 |
| Vector Database (0) | 2026.02.02 |
| LLM 모델 이해 및 활용(2) (0) | 2026.02.02 |

