Vue.js 기초: 프로젝트 설정부터 핵심 기능까지

환경 구축

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`로 프록시하여 요청합니다.

태그: Vue.js Node.js npm Vue CLI Vue Router

6월 1일 09:57에 게시됨