환경 구축
NodeJS와 npm을 설치합니다. NodeJS를 검색하여 다운로드하고 설치하면 npm도 함께 설치됩니다. 설치가 성공적으로 완료되었는지 명령 프롬프트(cmd)에서 확인할 수 있습니다.
NodeJS 설치가 완료되면, Vue 개발 도구를 설치합니다:
npm install 명령을 실행할 때 기본적으로 외국 저장소를 사용합니다. 다음 코드를 사용하여 저장소를 중국의 Taobao 미러로 변경할 수 있습니다:
npm config set registry https://registry.npm.taobao.org
다음 명령어를 통해 Vue 프로젝트를 생성합니다:
npm install -g @vue/cli # 최초 한 번만 실행
vue create my-vue-app # Vue 프로젝트 생성
cd my-vue-app # 프로젝트 디렉토리로 이동
npm install # 의존성 설치 (프로젝트 생성 마지막 단계에서 자동 실행을 선택했다면 이 단계는 생략 가능)
npm run serve # 개발 서버 실행
실행이 성공하면, 브라우저에 http://localhost:8080을 입력하여 기본 페이지를 확인할 수 있습니다.
Vue 프로젝트가 생성되면 WebStorm으로 프로젝트를 열 수 있습니다. 프로젝트 구조는 다음과 같습니다:
node_modules: 프로젝트의 모든 의존성 패키지가 저장됩니다.public: 정적 자산 파일이 저장됩니다.index.html은 프로젝트의 진입점이자 유일한 HTML 파일입니다.src: 개발자가 작성하는 소스 코드가 저장됩니다. 이후 작업의 99.99%는 이 디렉토리에서 이루어집니다.src/assets: 이미지, 폰트 등 정적 리소스가 저장됩니다.src/components: 재사용 가능한 컴포넌트가 저장됩니다.src/views: 전체 페이지 컴포넌트가 저장됩니다.src/router: 라우팅 설정 파일이 저장됩니다.src/main.js: 애플리케이션의 진입점 JavaScript 파일입니다.package.json: 프로젝트의 의존성과 스크립트가 정의됩니다.
main.js의 내용은 다음과 같습니다:
// Vue 객체를 가져옵니다.
import { createApp } from 'vue'
// 메인 컴포넌트를 가져옵니다.
import App from './App.vue'
// 라우터 설정을 가져옵니다.
import router from './router'
// 애플리케이션 인스턴스를 생성합니다.
const app = createApp(App)
// 라우터를 애플리케이션에 추가합니다.
app.use(router)
// '#app' ID를 가진 DOM 요소에 애플리케이션을 마운트합니다.
app.mount('#app')
프로젝트 실행 후 보이는 페이지는 App.vue (이제 MainComponent.vue로 가정)에 정의됩니다.
Vue 컴포넌트는 세 부분으로 구성됩니다: 1. 템플릿(template); 2. 스크립트(script); 3. 스타일(style)
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<!-- 라우터 뷰는 현재 URL에 따라 해당 컴포넌트를 렌더링할 위치입니다. -->
<router-view/>
</div>
</template>
<script>
export default {
name: 'MainComponent'
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
라우터 설정은 router/index.js 파일에 있습니다:
import { createRouter, createWebHistory } from 'vue-router'
import HomePage from '../views/HomePage.vue'
const routes = [
{
path: '/',
name: 'HomePage',
component: HomePage
}
]
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
데이터 바인딩
단방향 바인딩 (v-bind)
v-bind는 HTML 속성 값을 Vue 인스턴스의 데이터에 동적으로 연결합니다. :로 축약할 수 있습니다.
<div id="app">
<div v-bind:style="textStyle">단방향 바인딩</div>
<!-- 축약형 -->
<div :style="textStyle">단방향 바인딩</div>
</div>
<script>
const app = createApp({
data() {
return {
textStyle: 'color: blue; font-size: 20px;'
}
}
})
app.mount('#app')
</script>
양방향 바인딩 (v-model)
v-model은 입력 요소의 값을 Vue 인스턴스의 데이터와 양방향으로 연결합니다.
<div id="app">
<p>입력 값: {{ keyword }}</p>
<input type="text" v-model="keyword">
</div>
<script>
const app = createApp({
data() {
return {
keyword: 'Vue.js'
}
}
})
app.mount('#app')
</script>
이벤트 처리
v-on 디렉티브는 DOM 이벤트를 수신하고 JavaScript 메서드를 호출합니다. @로 축약할 수 있습니다.
<div id="app">
<button v-on:click="handleClick">클릭</button>
<button @click="handleClick">클릭 (축약형)</button>
</div>
<script>
const app = createApp({
methods: {
handleClick() {
alert('버튼이 클릭되었습니다!');
}
}
})
app.mount('#app')
</script>
조건부 렌더링 (v-if)
v-if는 조건에 따라 요소를 렌더링하거나 제거합니다. v-else와 함께 사용할 수 있습니다.
<div id="app">
<input type="checkbox" v-model="isVisible">
<div v-if="isVisible">보이는 요소</div>
<div v-else>숨겨진 요소</div>
</div>
<script>
const app = createApp({
data() {
return {
isVisible: false
}
}
})
app.mount('#app')
</script>
리스트 렌더링 (v-for)
v-for는 배열 데이터를 기반으로 요소 목록을 렌더링합니다.
<div id="app">
<ul>
<li v-for="employee in employeeList" :key="employee.id">
{{ employee.name }} - {{ employee.position }}
</li>
</ul>
</div>
<script>
const app = createApp({
data() {
return {
employeeList: [
{ id: 1, name: '홍길동', position: '개발자' },
{ id: 2, name: '김철수', position: '디자이너' }
]
}
}
})
app.mount('#app')
</script>
라이프사이클 훅
Vue 컴포넌트는 생성, 마운트, 업데이트, 소멸 과정에서 특정 시점에 함수를 실행할 수 있습니다.
created: 컴포넌트가 생성된 직후, DOM에 마운트되기 전에 호출됩니다.mounted: 컴포넌트가 DOM에 마운트된 후에 호출됩니다.
<div id="app">
<p>{{ message }}</p>
</div>
<script>
const app = createApp({
data() {
return {
message: '안녕하세요!'
}
},
created() {
console.log('컴포넌트가 생성되었습니다.');
},
mounted() {
console.log('컴포넌트가 DOM에 마운트되었습니다.');
}
})
app.mount('#app')
</script>
컴포넌트
컴포넌트는 재사용 가능한 UI 조각입니다.
// 전역 컴포넌트 등록
const app = createApp({})
app.component('MyButton', {
template: '<button>클릭</button>'
})
// 지역 컴포넌트 등록
const app = createApp({
components: {
'MyHeader': {
template: '<header>웹사이트 헤더</header>'
}
}
})
// 컴포넌트 사용
<div id="app">
<MyHeader></MyHeader>
</div>
라우팅 (Vue Router)
Vue Router는 싱글 페이지 애플리케이션(SPA)에서 URL에 따라 다른 컴포넌트를 표시합니다.
// 라우터 설정
import { createRouter, createWebHistory } from 'vue-router'
import Dashboard from '../views/Dashboard.vue'
import UserManagement from '../views/UserManagement.vue'
const routes = [
{ path: '/', redirect: '/dashboard' },
{ path: '/dashboard', component: Dashboard },
{ path: '/users', component: UserManagement }
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
템플릿에서 라우터 링크와 뷰를 사용합니다:
<template>
<div>
<router-link to="/dashboard">대시보드</router-link>
<router-link to="/users">사용자 관리</router-link>
<router-view/>
</div>
</template>
HTTP 요청 (Axios)
Axios는 브라우저와 Node.js에서 사용할 수 있는 Promise 기반 HTTP 클라이언트입니다.
<div id="app">
<table>
<tr v-for="product in productList" :key="product.id">
<td>{{ product.name }}</td>
<td>{{ product.price }}</td>
</tr>
</table>
</div>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
const app = createApp({
data() {
return {
productList: []
}
},
created() {
this.fetchProducts()
},
methods: {
async fetchProducts() {
try {
const response = await axios.get('https://api.example.com/products')
this.productList = response.data
} catch (error) {
console.error('데이터를 가져오는 중 오류 발생:', error)
}
}
}
})
app.mount('#app')
</script>
API 요청 캡슐화
프로젝트에서는 API 요청을 별도의 서비스 파일로 캡슐화하여 관리하는 것이 좋습니다.
// api/product.js
import axios from 'axios'
const apiClient = axios.create({
baseURL: 'https://api.example.com',
headers: {
'Content-Type': 'application/json'
}
})
export default {
getProducts() {
return apiClient.get('/products')
},
getProduct(id) {
return apiClient.get(`/products/${id}`)
}
}
컴포넌트에서는 이 서비스를 사용합니다:
<script>
import productService from '@/api/product'
export default {
data() {
return {
products: []
}
},
async created() {
const response = await productService.getProducts()
this.products = response.data
}
}
</script>
인터셉터를 이용한 요청/응답 처리
Axios 인터셉터를 사용하여 모든 요청/응답을 중앙에서 처리할 수 있습니다.
// utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
// 요청 인터셉터
axios.interceptors.request.use(
config => {
// 요청을 보내기 전에 수행할 작업
return config
},
error => {
// 요청 오류 처리
ElMessage.error('요청 중 오류가 발생했습니다.')
return Promise.reject(error)
}
)
// 응답 인터셉터
axios.interceptors.response.use(
response => {
// 응답 데이터 처리
if (response.data.code !== 200) {
ElMessage.error(response.data.message || '서버 오류')
return Promise.reject(new Error(response.data.message || '서버 오류'))
}
return response.data
},
error => {
// 응답 오류 처리
if (error.response) {
switch (error.response.status) {
case 401:
ElMessage.error('인증 실패: 로그인이 필요합니다.')
break
case 404:
ElMessage.error('요청한 리소스를 찾을 수 없습니다.')
break
case 500:
ElMessage.error('서버 내부 오류가 발생했습니다.')
break
default:
ElMessage.error('알 수 없는 오류가 발생했습니다.')
}
}
return Promise.reject(error)
}
)
export default axios
크로스 오리진 (CORS) 처리
개발 환경에서 프론트엔드와 백엔드가 다른 도메인에서 실행될 경우 CORS 오류가 발생할 수 있습니다. Vue CLI 프로젝트에서는 `vue.config.js` 파일을 사용하여 프록시를 설정하여 해결할 수 있습니다.
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8081', // 백엔드 서버 주소
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
이 설정을 통해 `axios.get('/api/products')`와 같이 요청하면, 개발 서버는 이를 `http://localhost:8081/products`로 프록시하여 요청합니다.