01. Vue.js 개요 살펴보기
1.1 뷰란
Vue.js
- 뷰는 사용자 인터페이스(UI, User Interface)를 만드는 데 사용하는 자바스크립트 기반 오픈소스 프로그레시브 프레임워크
- 프로그레시브 프레임워크란 이미 다른 사양으로 개발된 웹 애플리케이션에서 일부분만 '점직적으로(Progressive)' 적용할 수 있도록 모듈화하고 유연한 구조를 갖춘 프레임워크를 의미

MVVM 아키텍처 패턴
뷰는 MVVM(Model-View-ViewModel) 아키텍처 패턴 기반을 두고 있습니다. 아래 3가지 구성요소로 이루어진 아키텍처 패턴입니다.
| 구성요소 | 뜻 | 역할 |
|---|---|---|
| Model | 모델 | 실제 데이터나 비즈니스 로직을 처리 (예: 사용자 정보, 서버에서 받은 데이터 등) |
| View | 뷰 | 사용자에게 보여지는 화면을 제공 (HTML, DOM) |
| ViewModel | 뷰모델 | View와 Model을 연결해주는 중간 관리자 역할 |
- ViewModel을 두어 UI와 데이터 처리 로직의 상호 의존성을 줄이는 아키텍처 패턴
- Model과 View 사이 상호 의존성을 낮추면 Model, View, ViewModel을 독립적으로 모듈화하고 재사용성이 높은 설계 가능
- Vue.js는 특히 ViewModel에 집중하는 프레임워크
MVVM 구성요소의 관계

- 사용자가 View에서 입력하면 → ViweModel이 받아서 → Model을 업데이트합니다.
- 반대로, Model이 바뀌면 → ViewModel이 감지해서 → View를 자동으로 갱신합니다.
Vue.js에서 MVVM 대응
| MVVM 구성 | Vue에서의 역할 |
|---|---|
| Model | data, props, 서버 응답 등 |
| View | <template>에 작성한 HTML |
| ViewModel | Vue 인스턴스(new Vue({ ... })) 또는 컴포넌트 내부 로직 |
1.3 뷰를 사용하는 이유
1.3.1 뷰의 특징
가상 DOM
웹 브라우저는 HTML 문서를 DOM(Document Object Model) 구조로 변경하여 화면에 출력합니다. HTML 문서는 브라우저에 의해 파싱되어 DOM 트리 형태로 메모리에 저장되고, 이를 통해 브라우저는 웹 페이지의 구조와 내용에 접근하고 조작할 수 있게 됩니다. 그러나 이 DOM을 직접 수정하면 많은 연산 비용이 발생합니다.
Vue.js는 실제 DOM이 아니라 가상의 DOM 객체를 먼저 변경하고, 변경된 최소한만 실제 DOM에 반영하여 렌더링 성능이 좋습니다.
양방향 데이터 바인딩
Vue.js는 MVVM 패턴 기반으로 View(화면)과 Model(데이터)가 서로 연결되어 있습니다.
간단한 예제 코드로 살펴보겠습니다.
<!-- View -->
<button @click="count++">Count is: {{ count }}</button>
// View Model
export default {
data() {
return {
count: 0
}
}
}
// Model
const data = {
count: 1
}
- 뷰에 해당하는 HTML 영역에서
click이벤트가 발생하면, 이를 중개하는 뷰 모델로 이벤트를 전달 - 뷰 모델은 이벤트에 의해 변경된 데이터(
count)를 감지하고, 이를 모델에 전달해 실제 내부 데이터를 변경 - 그리고 자신이 가진 데이터(
count)를 뷰의 데이터(count)와 동기화하여 화면을 업데이트 - 이 과정에서 뷰의 데이터(
count)와 뷰 모델의 데이터(count)를 양방향으로 연결해 값을 동기화
1.3.2 뷰의 장단점
장점
- CDN 방식으로도 쉽게 사용이 가능
script만으로 시작이 가능- 낮은 학습 곡선으로 HTML/CSS/JS 기본만 알아도 Vue 시작이 가능
- Vue, Router, Pinia 모두 한글 문서 제공
단점
- React보다 커뮤니티가 작고 중국에서 많이 사용됨
- 후원 기업이 부족하고 플러그인이 부족
1.3.3 뷰를 사용하는 실제 사이트
- 카카오: Nuxt 기반 SSR 웹
- 네이버 바이브: 뷰 기반 스트리밍 서비스
- 하나투어: Nuxt로 개발된 여행 사이트
- 줌(Zum): 포털 서비스도 Vue 기반
참고로 Vue Devtools 크롬 확장 프로그램 설치하면, 해당 사이트가 Vue 사용 중인지 확인이 가능합니다.
02. Vue.js 시작하기
2.2 첫 번째 뷰 애플리케이션 만들기
1. CDN(Content Delivery Network) 방식
- CDN은 전 세계 여러 서버에 웹 리소스(예: Vue.js)를 미리 저장해두고,
- 사용자가 요청할 때 가장 가까운 서버에서 빠르게 응답하는 시스템입니다.
CDN 방식의 특징
- 설치가 필요 없음. 단 한 줄로 Vue를 불러올 수 있음
- 빠르고 간단하게 테스트하거나 작은 프로젝트에 적합
CDN 사용 예시 코드
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
CND 장단점
| 장점 | 단점 |
|---|---|
| 설정이 매우 간단하고 빠름 | 규모가 커지면 유지보수 어려움 |
| 별도 빌드 도구 없이 바로 사용 가능 | 모듈 시스템을 쓰기 어렵고, 개발 도구 활용이 제한됨 |
2. NPM(Node Package Manager) 방식
- NPM은 Node.js의 패키지 매니저로, 필요한 라이브러리(예: Vue)를 설치하고 관리할 수 있게 도와줍니다.
- 일반적으로 Vite, Webpack 같은 빌드 도구와 함께 사용합니다.
3. NPM 방식의 특징
- 모듈화, 컴포넌트 기반 개발, 핫 리로딩 등 현대적인 개발환경 구성 가능
- 실무 프로젝트에서는 거의 항상 이 방식 사용
설치 명령어 (Vite 기반 예시)
npm create vue@latest
cd my-vue-app
npm install
npm run dev
NPM 장단점
| 장점 | 단점 |
|---|---|
| 대규모 개발에 적합한 구조 | 처음 설치 및 설정이 다소 복잡함 |
| Vue Devtools, Hot Reload 등 개발 도구 사용 가능 | Node.js와 NPM 환경이 필요함 |
2.2.3 CDN과 NPM의 비교
정리
| 항목 | CDN 방식 | NPM 방식 |
|---|---|---|
| 사용 대상 | 빠른 테스트, 소규모 | 실무, 중대형 프로젝트 |
| 설치 | 필요 없음 (링크만 추가) | NPM 설치 필요 |
| 장점 | 쉽고 빠르게 시작 가능 | 확장성, 유지보수 용이 |
| 단점 | 기능 제한, 비효율적인 구조 | 초기 설정이 필요 |
03. 뷰 기본 문법 익히기
3.1 뷰 애플리케이션의 기본 구조

Vue.js는 자동으로 생성되는 폴더 및 파일이 있습니다. 이 중에서 애플리케이션을 실행하는 데 특히 중요한 역할을 하는 파일은 package.json, index.html, main.js, App.vue입니다.
| 파일명 | 역할 |
|---|---|
package.json |
의존성, 스크립트, 프로젝트 정보 관리 |
index.html |
앱의 진입점이 되는 HTML 파일 |
main.js |
앱 초기화 및 컴포넌트 렌더링 |
App.vue |
루트 컴포넌트, 화면 구성 시작점 |
3.1.1 package.json
- 프로젝트의 기본 정보, 스크립트 명령어, 의존성 정보를 포함하는 파일

주요 항목 설명
| 항목 | 설명 |
|---|---|
name |
앱 이름 (자유롭게 작성 가능) |
version |
앱 버전, "major.minor.patch" 형식 |
private |
외부 공개 여부 (true이면 비공개) |
scripts |
명령어 정의 (npm run dev 등) |
dependencies |
앱 실행에 필요한 라이브러리 (예: vue) |
devDependencies |
개발 환경에서만 필요한 라이브러리 (예: eslint, vite 등) |
3.1.2 index.html
- 앱의 HTML 템플릿
- 페이지가 처음 로드될 때 실행되는 기본 HTML
중요 포인트
<!DOCTYPE html>
<html lang="en">
<head> <!-- 내용 생략 --> </head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
<div id="app">→ Vue 앱이 연결되는 루트 요소<script type="module" src="/src/main.js">→ 앱 초기화 JS 연결
3.1.3 main.js
- Vue 애플리케이션을 실행하고 초기화하는 파일
주요 코드 구성
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
| 부분 | 설명 |
|---|---|
createApp(App) |
Vue 인스턴스를 생성 |
.mount('#app') |
HTML의 id="app" 요소에 Vue 연결 |
App.vue |
루트 컴포넌트를 연결 |
main.js 파일이 실행되면 App.vue의 내용이 브라우저에 처음 표시됩니다.
3.1.4 App.vue
- 루트 컴포넌트(SFC: Single File Component)
- 화면을 구성하는 첫 출발점
App.vue는 아래 3가지 영역으로 나뉩니다.
<template> <!-- HTML 구조 -->
</template>
<script> <!-- 자바스크립트 로직 -->
</script>
<style> <!-- 스타일 정의 -->
</style>
이 안에서 또 다른 컴포넌트들을 불러오고 연결해 트리 형태로 화면 구성이 확장됩니다.
실행 흐름 요약
npm run dev ▶ index.html ▶ main.js ▶ App.vue
npm run dev→ 개발 서버 시작index.html→main.js스크립트 로딩main.js→App.vue불러와 Vue 인스턴스 실행- 브라우저에서 화면 렌더링
요약 정리(꼭 짚고 넘어가기)
| 구분 | 역할 요약 |
|---|---|
package.json |
앱 정보와 명령어 및 의존성 관리 |
index.html |
HTML 진입점, #app 루트 요소 포함 |
main.js |
앱 초기화 및 루트 컴포넌트 등록 |
App.vue |
앱의 시작 컴포넌트, 화면 구성 시작점 |
3.2 뷰 애플리케이션 인스턴스
Vue 애플리케이션 인스턴스는 Vue 앱을 시작하고, 화면에 렌더링하고, 내부 기능을 연결해주는 중심 객체입니다.
Vue를 사용하는 데 있어 가장 기초이자 중요한 개념이므로 단계별로 이해해야 합니다.
3.2.1 인스턴스란
- 자바스크립트에서 인스턴스(Instance)는 어떤 클래스로부터 만들어진 실제 객체를 의미
class는 설계도,instance는 실제 사용 가능한 객체
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`안녕하세요, 저는 ${this.name}입니다.`);
}
}
// Person 클래스의 인스턴스 생성
const person1 = new Person('Kim', 25);
person1.sayHello(); // 안녕하세요, 저는 Kim입니다.
이처럼 인스턴스는 특정 클래스의 상태를 나타내며, 해당 클래스에 정의된 속성과 메서드를 호출해 동작을 수행할 수 있습니다.
Vue에서의 의미
Vue는 자바스크립트 프레임워크입니다. 설치된 Vue 패키지를 사용해 뷰에서 사용할 수 있는 기능과 구성 요소가 포함된 인스턴스를 생성합니다. Vue에서 인스턴스를 생성하는 코드는 main.js 파일에 작성합니다.
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
- Vue에서는
createApp()함수를 통해 Vue 인스턴스를 생성함 - 인스턴스를 생성한 후
.mount('#app')을 통해 HTML DOM과 연결
create(App).mount(#app);으로 이제 뷰 패키지에 내장된 여러 기능과 구성 요소를 뷰 애플리케이션에서 사용할 수 있도록 생성한 인스턴스가 진입점 역할을 하게 됩니다.
3.2.2 인스턴스의 구성 요소
createApp() 함수를 사용해 애플리케이션 인스턴스를 생성할 때, App.vue 파일만 매개변수로 전달했습니다. 그런데 createApp() 함수를 사용할 때 인스턴스의 초기 설정을 담은 객체 {}를 직접 전달할 수도 있습니다.
import { createApp, h } from 'vue';
createApp({
data() {
return {
message: 'Hello, Vue!'
};
},
methods: {
reverse() {
this.message = this.message.split('').reverse().join('');
}
},
render() { // data와 methods에 정의한 속성 사용
return h('div', [
h('p', this.message),
h('button', { onClick: this.reverse }, 'Reverse')
]);
}
}).mount('#app');
인스턴스의 구성 요소 설명
| 항목 | 설명 |
|---|---|
data() |
Vue 인스턴스가 관리할 상태 데이터 |
methods |
동작 또는 이벤트 핸들러 함수 |
render() |
HTML 구조를 JS 코드로 작성 (직접 렌더링) |
위 코드처럼 직접 인스턴스를 구성해도 되지만, 구조가 복잡해지고 재사용이 어려워집니다. 이처럼 인스턴스를 직접 코드에 구성하면 가독성과 유지보수성이 떨어지는 단점을 극복하기 위해 Vue는 SFC(Single File Component)라는 독자적인 파일 형식을 만들었는데, 바로 App.vue 파일입니다.
SFC 파일인 App.vue 파일 안의 설정 정보를 가져와 인스턴스를 생성합니다. 이 방법으로 작성한 코드가 바로 main.js 파일입니다.
import './assets/main.css'
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
3.2.3 SFC의 구조
SFC는 뷰만의 특별한 파일 형태로, 확장자가 vue인 단일 구성 요소입니다. 단일 구성 요소는 하나의 파일에 인스턴스 구성 요소와 관련한 모든 코드가 포함되는 형식을 의미합니다. SFC는 일반적으로 <script>, <template>, <style> 총 3개 태그 영역으로 구성됩니다. 이때 주의할 점은 SFC 파일에 <template> 태그를 1개 이상 포함해야 합니다. 그리고 <script> 태그와 <style> 태그는 선택 사항입니다.
.vue파일 하나에template(필수),script,style을 모아서 관리- 예:
App.vue를 만들어 인스턴스 설정을 별도로 관리
<script> 태그 영역
- 자바스크립트 코드 작성 영역
- 뷰에서만 사용할 수 있는 문법 규칙을 지키며 코드를 작성해야 함
<template> 태그 영역
- HTML 코드를 작성하는 영역
<style> 태그 영역
<template>태그 영역의 구성 요소에 CSS 스타일을 적용하기 위한 영역- 뷰에서만 적용되는 CSS 범위에 따른 구분을 배워야 함
3.3 기본 문법 다루기
3.3.2 Options API 사용하기
SFC 파일인 App.vue 파일 안의 <script> 태그 영역은 로직을 자바스크립트로 작성합니다. 이때 사용하는 문법이 두 가지가 있는데, 그 중 Options API를 학습합니다. Options API는 다음과 같이 export default 키워드로 내보내는 객체 {} 안에 여러 속성을 사용합니다.
<script>
export default {};
</script>
<template></template>
<style></style>
자바스크립트 객체는 자유롭게 속성을 정의할 수 있습니다. 하지만 Vue에서 export default 키워드로 내보내는 객체는 Vue에서 약속된 속성을 사용해야만 합니다. Options API에서 제공하는 속성은 다음과 같습니다.
| 속성 | 설명 |
|---|---|
data |
데이터(data)를 저장하는 속성 |
method |
함수(function)를 저장하는 속성 |
computed |
데이터를 기반으로 계산된(computed) 속성 |
watch |
데이터의 변화를 감시(watch)하는 속성 |
props |
사용자 정의 데이터를 수신하기 위한 속성 |
**emits** |
발신 가능한 부모 컴포넌트의 이벤트를 정의하기 위한 속성 |
provide |
하위 컴포넌트에 데이터를 주입하기 위한 속성 |
inject |
상위 컴포넌트에 주입된 데이터를 받기 위한 속성 |
Life Cycle Hooks |
컴포넌트의 특정 시점에 따라 자동으로 호출되는 콜백 함수 |
컴포지션 API
컴포지션 API(Composition API)도 SFC 파일의 <script> 태그 영역에 자바스크립트 코드를 작성하는 문법 중 한 가지입니다. Vue 3에서 도입됐으며, 실무에서도 많이 사용하는 문법입니다. 그러나 Options API 또한 현업에서 여전히 사용되므로 먼저 배우도록 하겠습니다.
3.3.3 데이터 정의하기
<script>태그 영역에 로직을 작성할 때 가장 기본이 되는 개념은 데이터. (데이터 = 컴포넌트 전반에 걸쳐서 사용할 값)- JS →
var,let,const키워드 중 한 개를 선택 - Vue →
data옵션 속성을 사용
src/App.vue
data 옵션 속성 사용
<script>
export default {
data: function () {
return {};
},
};
</script>
data 옵션 속성: ES6 메서드 축약 표현
ES6의 단축 메서드 이름을 사용해 간단하게 작성할 수 있습니다.
<script>
export default {
data() {
return {};
},
};
</script>
data 옵션 속성: return 작성
data 속성에서 return 문으로 반환되는 객체에 정의된 속성이 SFC에서 사용할 수 있는 데이터 속성입니다.
<script>
export default {
data() {
return {
name: "철수",
age: 19,
};
},
};
</script>
따라서 data 옵션 속성의 return 문에 정의된 객체의 속성이 컴포넌트에서 사용할 수 있는 값이 됩니다. Vue에서는 name과 age를 상황과 문맥에 따라 변수 혹은 데이터 또는 데이터 속성이라고도 합니다. 객체의 속성 값으로 사용하는 종류는 자바스크립트에서 변수에 할당할 수 있는 값(문자열, 숫자, 논리형 등)과 같습니다.
3.3.4 데이터 보간 이용하기
data옵션 속성으로 선언한 데이터 속성은<template>태그 영역에서 사용 가능- Vue에서 제공하는 텍스트 보간(Text Interpolation)
{{}}을 사용
{{ 데이터 속성 }} // mustach syntax
<template>
<p>내 이름은 {{ name }} 입니다.</p>
<p>나이는 {{ age }}살 입니다.</p>
{{ 10 * 20 * 30 }}
{{ name.toUpperCase() }}
</template>
3.3.5 디렉티브 사용하기
<script>태그 영역에서 정의한data옵션 속성을<template>영역에서 HTML 태그의 속성처럼 사용- 디렉티브는 항상 HTML 태그의 속성으로 사용
- Vue에서만 사용하는 특별한 문법
- 항상
v-접두사가 붙음 (예:v-if,v-for)
1. v-html
- HTML 문자열을 실제 태그로 렌더링할 수 있게 함
{{ message }}와 달리, 문자열 안에 포함된 태그도 브라우저가 해석함
⚠️ 주의 사항
- XSS(크로스 사이트 스크립팅) 공격에 매우 취약
- 사용자 입력 데이터에는 절대 사용하지 말 것
- 개발자가 작성한 신뢰 가능한 HTML만 사용해야 안전
문자열 출력 코드
<script>
export default {
data() {
return {
message: "<h1>Hello, Vue JS!</h1>",
};
},
};
</script>
<template>
{{ message }}
</template>
<style></style>

문자열 출력 코드: v-html
<template>
<div v-html="message"></div>
</template>

2. v-text
- 값을 텍스트로만 출력함
- HTML 태그가 포함된 문자열도 그냥 문자열로 출력됨
<div v-text="message"></div>

v-text vs {{ message }} 차이
| 항목 | v-text |
{{ message }} |
|---|---|---|
| 적용 위치 | 전체 텍스트 대체 | 태그 내부 일부분에도 사용 가능 |
| 장점 | 간결한 속성 스타일 | 더 유연하게 조합 가능 |
<!-- v-text는 전체 내용을 덮어씀 -->
<p v-text="message"></p>
<!-- {{ message }}는 일부에만 사용 가능 -->
<p>메시지: {{ message }}</p>
**v-html와 v-text 비교**
| 항목 | v-html |
v-text |
|---|---|---|
| 목적 | HTML 렌더링 | 텍스트 출력 |
| 태그 해석 | O (브라우저가 태그로 인식함) | X (그냥 글자로 출력됨) |
| 위치 | 요소 내부 속성으로 사용 |
요소 내부 속성으로 사용 |
| 보안 위험 | XSS 위험 있음 ❌ | 안전함 ✅ |

정리
| 디렉티브 | 기능 | 특징 | 주의사항 |
|---|---|---|---|
v-html |
HTML을 실제 태그로 렌더링 | 태그가 해석되어 적용됨 | XSS 공격 위험 (사용자 입력 주의) |
v-text |
텍스트로 출력 | 태그도 문자열 그대로 출력 | 전체 요소 내용이 덮어짐 |
{{ message }} |
텍스트 출력 (템플릿 문법) | 템플릿에서 자주 사용 | 태그 내부 일부로 사용 가능 |
3. v-pre
<template>영역 안의 특정 태그를 Vue가 컴파일하지 않고 건너뛰게 하는 용도- Vue 컴파일러가 해당 영역을 무시하기 때문에 성능 최적화에 도움
v-pre 사용 목적
- 렌더링 속도 향상: 불필요한 컴파일을 방지하여 초기 렌더링 속도를 높임
- 문법 예시 출력: Vue 문법을 실제 코드처럼 보여주고 싶을 때 사용
<template>
<div v-pre>{{ message }}</div>
</template>

message가 컴파일되지 않고, 단순한 문자열 그대로 출력됩니다.
정리
| 항목 | 내용 |
|---|---|
| 디렉티브 이름 | v-pre |
| 기능 | Vue가 해당 HTML을 컴파일하지 않고 건너뜀 |
| 사용 이유 | 성능 최적화, 코드 예시 출력 등에 사용 |
| 결과 | 데이터 바인딩이 되지 않고 그대로 출력됨 (예: {{ message }}) |
| 주의 | 실제 화면에 표현되길 원할 경우 v-pre를 사용하지 말아야 함 |
4. v-bind
{{}}는 data 옵션 속성으로 정의한 데이터 속성의 값을 <template> 태그 영역에서 HTML 태그의 속성 값으로 지정할 수 없습니다. 그래서 다음과 같이 작성하면 문법 오류가 발생하게 됩니다.
<template>
<h1 class={{ className }}>안녕하세요</h1> <!-- 문법 오류 발생 -->
</template>
HTML 태그의 컨텐츠(Content)가 아니라 속성(Attribute)에 데이터를 연결하고 싶다면 v-bind 디렉티브를 사용해야 합니다. v-bind 특징과 사용 방법을 살펴보도록 하겠습니다.
v-bind는 HTML 태그의 속성(attribute)에 데이터 속성을 연결할 때 사용- 보통
class,id,src,href,disabled등 HTML 속성에 값을 동적으로 연결할 때 사용 - 항상 HTML 속성 앞에
v-bind:를 붙여 사용하며, 축약형으로:만 써도 동일하게 동작
v-bind 문법
<template>
<h1 v-bind:class="className">안녕하세요</h1>
</template>
v-bind 예제
<script>
export default {
data() {
return {
className: "red-color",
};
},
};
</script>
<template>
<div v-bind:class="className">Hello, Vue JS</div>
</template>
<style>
.red-color {
color: red;
}
</style>


v-bind 축약형 문법
<!-- 기본형 -->
<h1 v-bind:class="className">Hello, Vue JS</h1>
<!-- 축약형 -->
<h1 :class="className">Hello, Vue JS</h1> <!-- 동일한 결과 -->
실무에서는 축약형 : 문법을 더 자주 사용합니다.
v-bind 속성 이름 = 값이 같을 경우 단축
<script>
export default {
data() {
return {
class: "red-color", // 키 값을 class로 변경
};
},
};
</script>
<template>
<div :class>Hello, Vue JS</div> <!-- 값이 같을 경우 가능 -->
</template>
<style>
.red-color {
color: red;
}
</style>
Vue 3.4부터는 속성과 값이 동일한 이름이면 다음처럼 더 간단하게 쓸 수 있어요.
정리
| 항목 | 내용 |
|---|---|
| 디렉티브 이름 | v-bind |
| 목적 | HTML 속성에 데이터를 동적으로 바인딩 |
| 기본 문법 | v-bind:속성명="값" |
| 축약 문법 | :속성명="값" |
| 속성=값 같을 때 | :속성명만으로도 가능 (Vue 3.4 이상) |
| 사용 예시 | class, id, href, src, disabled 등 |
5. v-if, v-else-if, v-else
v-if,v-else-if,v-else는 Vue 템플릿에서 조건에 따라 요소를 선택적으로 렌더링- 자바스크립트의
if,else if,else문과 유사한 구조 - 조건이 참일 경우에만 해당 HTML 요소가 렌더링되고, 그렇지 않으면 DOM에 아예 포함되지 않음
<script>
export default {
data() {
return {
visible: true,
unVisible: false,
};
},
};
</script>
<template>
<p v-if="visible">이 요소는 렌더링됩니다.</p>
<p v-if="unVisible">이 요소는 렌더링되지 않습니다.</p>
</template>
<style></style>


이때 화면에 보이지 않는(unvisible) 요소는 DOM에 추가된 다음에 감춰지는 것이 아니라 아예 DOM에 추가되지 않는 방식으로 처리됩니다. 개발자 도구를 열어 다음과 같이 v-if 부분이 주석 처리되어 요소가 DOM에 추가되지 않았다는 것을 알 수 있습니다.
v-if, v-else-if, v-else 사용 방법
<script>
export default {
data() {
return {
condition: "A",
};
},
};
</script>
<template>
<p v-if="condition === 'A'">condition 데이터의 값은 A 입니다.</p>
<p v-else-if="condtion === 'B'">condition 데이터의 값은 B 입니다.</p>
<p v-else-if="condtion === 'B'">condition 데이터의 값은 C 입니다.</p>
<p v-else-if="condtion === 'A'">condition 데이터의 값은 또 다시 A 입니다.</p>
<p v-else>어떤 조건에도 해당하지 않습니다.</p>
</template>
<style></style>
condition: 'A'일 경우 → "A입니다" 출력 후 종료condition: 'D'일 경우 →else블록 실행됨
DOM 처리 방식 차이
| 비교 항목 | 설명 |
|---|---|
v-if |
조건이 false이면 DOM에 아예 생성되지 않음 |
v-show (참고) |
조건이 false여도 DOM에 존재하되 style="display: none;" 처리 |
v-if는 성능을 위해 렌더링 비용이 클 때, v-show는 자주 토글되는 요소에 적합합니다.
정리
| 디렉티브 | 설명 | 특징 |
|---|---|---|
v-if |
조건이 참일 때만 요소를 렌더링 | 조건이 false이면 DOM에 추가되지 않음 |
v-else-if |
앞의 조건이 false일 때, 또 다른 조건 검사 | 여러 조건 분기 가능 |
v-else |
앞 조건이 모두 false일 때 실행 | 조건 없음, 반드시 v-if 또는 v-else-if 뒤에 사용해야 함 |
6. v-show
v-if처럼 조건부 렌더링 기능을 구현- 할당된 값의
true혹은false에 따라 요소를 선택적으로 렌더링
<script>
export default {
data() {
return {
condition: true,
};
},
};
</script>
<template>
<p v-show="condition">show 디렉티브는 조건이 참입니다.</p>
<p v-show="!condition">show 디렉티브는 조건이 거짓입니다.</p>
</template>
<style></style>


7. v-cloak
v-cloak은 Vue 애플리케이션에서 렌더링이 완료되기 전에 데이터가 화면에 노출되지 않도록 막기 위해 사용- 주로 Vue를 CDN 방식으로 불러오는 경우 렌더링이 완료되기 전에
{{ message }}같은 원본 틀이 잠깐 보이는 깜빡임 현상(Flickering)을 방지합니다.
v-cloak 사용 이유
- Vue는 JavaScript 실행 이후에 템플릿을 HTML로 렌더링합니다.
- 이때 JS 실행 전에 HTML만 먼저 보이면 사용자는 "이상한 코드"를 보게 됩니다.
v-cloak은 렌더링 완료 전까지 해당 요소를 숨기고 있다가, Vue가 바인딩을 마치면 자동으로 속성을 제거하여 화면에 정상 출력되도록 합니다.
사용 방법
<!-- HTML -->
<style>
[v-cloak] {
display: none;
}
</style>
<div id="app">
<h1 v-cloak>{{ message }}</h1>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
window.setTimeout(() => {
createApp({
data() {
return { message: 'Hello Vue!' };
}
}).mount("#app");
}, 2000); // 일부러 2초 지연시켜 봄
</script>
정리
| 항목 | 설명 |
|---|---|
| 목적 | 초기 렌더링 전 {{ }} 문법이 그대로 노출되는 것 방지 |
| 작동 방식 | 렌더링 완료 전에는 숨기고, 완료되면 자동으로 제거 |
| 사용 시기 | 주로 CDN 방식 Vue 사용 시 필요 |
| 스타일 필요 | [v-cloak] { display: none; } 를 CSS에 반드시 정의 |
8. v-for
v-for는 Vue에서 배열이나 객체 데이터를 반복 출력할 때 사용- HTML 요소를 반복적으로 생성할 수 있도록 제공
- 반드시 key 속성을 사용해 고유한 값이 할당되도록 해야 함
배열 반복 예시
<script>
export default {
data() {
return {
fruits: ["apple", "bnana", "orange"],
};
},
};
</script>
<template>
<ul>
<li v-for="(fruit, index) in fruits" :key="index">
인덱스: {{ index }}, 값: {{ fruit }}
</li>
</ul>
</template>
<style></style>
주의사항
key속성은 필수! → DOM 업데이트 최적화를 위해 반드시 필요- index를
key로 쓰는 경우는 단순한 리스트일 때만 적절

객체 반복 예시
<script>
export default {
data() {
return {
user: {
name: "John",
age: 20,
gender: "male",
},
};
},
};
</script>
<template>
<h1>user 데이터의 반복 결과</h1>
<ul>
<li v-for="(value, key, index) in user" :key="index">
{{ index }}: {{ key }} = {{ value }}
</li>
</ul>
</template>
객체일 때 순서 중요
- 반드시
value가 맨 앞에 와야 함:(value, key, index) - key와 index는 생략 가능하지만 순서 보장 필수
:key는 유일한 값을 주도록key나index중 하나를 지정

정리
| 항목 | 설명 |
|---|---|
| 목적 | 배열 또는 객체 데이터를 반복 렌더링 |
| 배열 반복 | (item, index) in 배열 형식 |
| 객체 반복 | (value, key, index) in 객체 형식 |
| key 속성 | 반드시 고유한 값 필요 (DOM 최적화 위해) |
| 필수 위치 | value는 반드시 맨 앞에 위치해야 함 |
3.4 이벤트 다루기
Vue에서 이벤트는 사용자의 동작(클릭, 키 입력 등)에 반응하기 위해 사용됩니다.
자바스크립트에서는 addEventListener() 등을 사용하지만, Vue에서는 v-on 디렉티브로 더 간단하게 처리할 수 있습니다.
3.4.1 v-on 디렉티브와 methods 옵션 속성으로 이벤트 연결하기
문법
v-on:이벤트_타입="핸들러_함수명"
✔ 예제 1: 클릭 이벤트 연결
<template>
<button v-on:click="clickHandler">클릭</button>
</template>
<script>
export default {
methods: {
clickHandler() {
alert('click!');
}
}
}
</script>
**
단축형 문법**
<!-- 아래 둘은 동일 -->
<button v-on:click="clickHandler">클릭</button>
<button @click="clickHandler">클릭</button>
더블클릭 같은 이벤트도 가능
<button @dblclick="clickHandler">클릭</button>
3.4.2 이벤트 객체 사용하기
이벤트 객체란?
사용자가 발생시킨 이벤트에 대한 자세한 정보를 담고 있는 객체입니다.
이 객체는 Vue에서 핸들러 메서드에 $event로 전달할 수 있습니다.
✔ 예제 2: 이벤트 객체 출력
<template>
<button @click="clickHandler($event)">클릭</button>
</template>
<script>
export default {
methods: {
clickHandler(event) {
console.log(event); // PointerEvent 등 출력됨
}
}
}
</script>
참고
@click="clickHandler"→ 자동으로 event 객체를 첫 번째 인자로 넘겨줌@click="clickHandler('Hello')"처럼 일반 값을 넘기면, event 객체는 전달되지 않음!($event 객체로 넘기자!)
이벤트 객체와 값 함께 전달
✔ 예제 3: 이벤트 객체 + 커스텀 메시지 함께 전달
<template>
<button @click="clickHandler($event, 'Hello')">클릭</button>
</template>
<script>
export default {
methods: {
clickHandler(event, message) {
console.log(event); // PointerEvent
console.log(message); // 'Hello'
}
}
}
</script>
이벤트 객체($event)를 전달하지 않고 일반 매개변수만 전달하면 사용할 수 없습니다.
이벤트 객체와 값을 함께 사용해야만 합니다.
함수 vs 메서드
| 항목 | 설명 |
|---|---|
함수(function) |
독립적으로 정의된 코드 블록 |
메서드(method) |
객체(여기선 Vue 컴포넌트)의 속성으로 정의된 함수 |
function add(a, b) { // 함수
return a + b;
}
const calculator = {
add(a, b) { // 메서드
return a + b;
},
}
Vue에서는 methods 옵션 안에 정의된 함수는 메서드로 부릅니다.
요약
| 구분 | 설명 |
|---|---|
v-on:event="method" |
이벤트를 연결하는 Vue 문법 |
@event="method" |
v-on의 단축형 |
$event |
이벤트 객체를 넘길 때 사용 |
| 커스텀값 + 이벤트객체 | @click="handler($event, 'msg')" |
3.4.3 수식어 사용하기
- 수식어는 이벤트 동작을 제어하는 기능
- 수식어를 사용하면 일반적인 DOM 이벤트 동작을 변경하거나 기능을 추가할 수 있음
수식어 기본 문법
@이벤트타입.수식어="핸들러"
수식어 예제: 키보드 입력 시 Enter 감지
❌ 수식어 없이 처리
<script>
export default {
methods: {
onKeyupHandler(e) {
if (e.keyCode === 13) { // 키보드 이벤트 객체에서 KeyCode 속성 13은 Enter 키
console.log('Enter!');
}
},
},
}
</script>
<template>
<input type="text" @keyup="$event => onKeyupHandler($event)" />
</template>
✅ 수식어 .enter 사용
<script>
export default {
methods: {
onKeyupHandler() {
console.log('Enter!');
},
},
}
</script>
<template>
<input type="text" @keyup.enter="onKeyupHandler" />
</template>
수식어 종류 정리
| 구분 | 수식어 | 설명 |
|---|---|---|
| 이벤트 수식어 | .stop |
이벤트 버블링 방지 (상위로 전파 X) |
.prevent |
기본 동작 제거 (form 제출 등) |
|
.self |
이벤트가 발생한 요소 자신에게만 반응 | |
.once |
이벤트가 단 1회만 실행됨 | |
.passive |
수동 이벤트 (예: 스크롤 시 성능 개선용) | |
| 키보드 입력 키 | .enter |
Enter 키일 때만 실행 |
.tab |
Tab 키 |
|
.delete |
Delete 또는 Backspace 키 |
|
.space |
Space 키 |
|
| 시스템 키 | .up |
↑ 방향키 |
.down |
↓ 방향키 | |
.left |
← 방향키 | |
.right |
→ 방향키 | |
.ctrl |
Ctrl 키 |
|
.alt |
Alt 키 |
|
.shift |
Shift 키 |
|
.meta |
Win 또는 Command(⌘) 키 |
|
| 특별 수식어 | .exact |
정확한 키 조합일 때만 처리 |
| 마우스 버튼 | .left |
마우스 왼쪽 버튼 클릭 시 |
.right |
마우스 오른쪽 버튼 클릭 시 | |
.middle |
마우스 가운데(휠) 버튼 클릭 시 |
TIP: Vue의 수식어는 DOM 이벤트를 더욱 직관적으로 다루도록 도와줍니다. (예: @click.prevent → 클릭 시 기본 동작 제거)
3.4.4 이벤트와 반응성
1. 반응성(Reactivity)
- Vue는 데이터의 변화를 자동 감지하여 화면을 업데이트
- 이 기능은 Vue의 반응성 시스템 덕분에 가능
data()에서 정의된 값을 뷰가 자동으로 감지하고 UI에 반영
2. this 사용
methods: {
increasement() {
this.number++; // this를 통해 data 속성 접근
}
}
- 메서드 안에서
data()의 속성 접근 시this사용이 필수입니다.
v-once: 한 번만 렌더링
- 요소를 한 번만 렌더링하고 이후에는 갱신하지 않음
- 반응성이 적용되지 않아, 데이터는 바뀌어도 화면은 그대로 유지됨
<h1 v-once>{{ number }}</h1>
활용: 바뀌지 않는 정적인 데이터에 사용합니다.
v-memo 디렉티브: 렌더링 결과 메모이제이션 (Vue 3.2 이상)
- 특정 조건이 변경되지 않으면 다시 렌더링하지 않음
- 조건이 만족될 때만 요소를 렌더링
<div v-memo="[name, gender]">
<p>이름: {{ name }}</p>
<p>성별: {{ gender }}</p>
<p>나이: {{ age }}</p>
</div>
v-memo 특징
[name, gender]값이 바뀌어야만 다시 렌더링됨age만 바뀌어도 UI는 렌더링되지 않음- 성능 최적화 용도로 사용
v-memo 공식 권장: 한 번에 1000번 이상 반복되는 요소에 사용할 때 의미 있습니다.
정리
| 디렉티브 | 설명 | 사용 시점 |
|---|---|---|
v-once |
요소를 최초 한 번만 렌더링 | 절대 바뀌지 않을 정적 요소 |
v-memo |
특정 조건이 바뀔 때만 렌더링 | 성능 최적화를 원할 때 |
3.5 폼 다루기
3.5.1 폼 이해하기
- HTML에서 폼은
<form>태그로 생성 - 사용자가 값을 입력하면 이를 서버로 전송하거나 클라이언트에서 처리
폼 전송 방식은 2가지
✔️ 첫 번째 방법: action 속성으로 서버에 바로 전송 (전통적인 방식)
<!-- form_ex_1.html -->
<form action="/login_process.php">
<label for="uid">아이디: <input type="text" id="uid"/></label>
<label for="upw">비밀번호: <input type="password" id="upw"/></label>
<button type="submit">로그인</button>
</form>
<form action="URL">속성을 사용하여 값을 서버로 직접 전송- 사용자가 입력을 완료하고 제출하면 다른 페이지로 이동하면서 전송
- 단점: 페이지가 이동되기 때문에 화면이 새로고침
📷 결과는 첫 번째 방법으로 실행 시, 입력값을 적고 로그인 버튼을 누르면 새로운 페이지/login_process.php로 이동해요.
✔️ 두 번째 방법: JavaScript + AJAX 로 전송 (비동기 방식)
<!-- form_ex_2.html -->
<form id="loginForm">
<label for="uid">아이디: <input type="text" id="uid"/></label>
<label for="upw">비밀번호: <input type="password" id="upw"/></label>
<button type="submit">로그인</button>
</form>
<script>
document.ElementById('loginForm').addEventListener('submit', function (event) {
event.preventDefault(); // 폼 전송 이벤트 취소
// 요소에 입력된 값 가져오기
const uid = document.getElementById('uid').value;
const upw = document.getElementById('upw').value;
// AJAX 요청 만들기
const xhr =new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
// 요청이 성공할 경우
const response = xhr.responseText;
console.log(response);
} else {
// 요청이 실패할 경우
console.log('Request failed with status ' + xhr.status);
}
}
};
// 데이터 전송 준비하기
const formData = new FormData();
formData.append('uid', uid);
formData.append('upw', upw);
// AJAX로 데이터 전송 요청하기
xhr.send(formData);
});
</script>
- 이 방식은 페이지 새로고침 없이 입력 데이터를 서버로 보낼 수 있음
- 사용자 경험이 훨씬 부드럽고 깔끔
- Vue에서는 이처럼 비동기 방식을 자주 사용하며, 데이터를 Vue 내부에서 직접 처리하는 방식으로 연결됨
결과 화면은 AJAX 방식도 겉으로는 동일해 보이지만, 화면은 그대로 유지되고 서버와 통신만 이루어져요.
요약
| 구분 | 방식 | 특징 |
|---|---|---|
| 전통적인 폼 전송 | <form action="..."> |
페이지 이동 발생, 단순하지만 새로고침 있음 |
| 비동기 전송 (AJAX) | JavaScript + XMLHttpRequest | 페이지 이동 없이 처리 가능, 복잡하지만 사용자 경험 우수 |
3.5.2 v-model 디렉티브로 폼 요소 다루기
v-model
v-model은 양방향 데이터 바인딩을 가능하게 하는 Vue의 디렉티브- 즉, 사용자가
<input>이나<textarea>등에 입력한 값이 Vue 컴포넌트의data속성과 자동으로 연결되고, - 반대로,
data속성의 값이 바뀌면 화면의 입력 요소에도 즉시 반영됨
1) 한 줄 입력 요소 다루기 (예: 아이디, 비밀번호 입력)
<script>
export default {
data() {
return {
uid: "",
upw: "",
};
},
methods: {
login() {
console.log(`id: ${this.uid}`);
console.log(`pw: ${this.upw}`);
},
},
};
</script>
<template>
<form id="loginForm">
<label for="uid">아이디: <input type="text" id="uid" v-model="uid" /></label>
<label for="upw">비밀번호: <input type="password" id="upw" v-model="upw" /></label>
<button type="button" @click="login">로그인</button>
</form>
</template>
v-model="uid":uid라는 data 속성과 입력창을 연결@click="login": 로그인 버튼을 클릭했을 때login()메서드가 실행- 콘솔에는 사용자가 입력한 값이 출력
📷 결과는 브라우저에서 로그인 버튼을 클릭하면 콘솔 창에 다음과 같은 메시지가 출력됩니다.

Vue에서는 v-model을 사용하면 사용자 입력값을 즉시 가져올 수 있으며, JavaScript로 직접 DOM 요소를 조작하지 않아도 돼요. 매우 효율적이죠!
2) 초기값이 있을 경우 바인딩 확인
<script>
export default {
data() {
return {
uid: 'sucoding',
upw: '',
};
},
};
</script>
- 위처럼
uid의 초기값을 설정하면 화면에 해당 값이 미리 입력된 상태로 보여집니다. - 양방향 바인딩 덕분에
v-model이 적용된input필드는 자동으로 초기값을 렌더링합니다.
📷 브라우저 화면에서 아이디 입력칸에 "sucoding"이 자동으로 채워져 있는 것을 확인할 수 있어요.

1) 체크박스 (Checkbox)
- 체크박스는 사용자에게 복수 선택 또는 단일 체크 여부를 입력받기 위해 사용
① 단일 체크 여부 (Boolean 값 바인딩)
<script>
export default {
data() {
return {
agree: false,
};
},
};
</script>
<template>
<label>
<input type="checkbox" v-model="agree" />
이용약관에 동의합니다
</label>
<p>동의 여부: {{ agree }}</p>
</template>
v-model은 체크 여부에 따라true또는false값을 자동으로 바인딩해줍니다.
② 여러 개의 체크박스 (배열 값 바인딩)
<script>
export default {
data() {
return {
selectedFruits: [],
};
},
};
</script>
<template>
<label><input type="checkbox" value="banana" v-model="selectedFruits" />바나나</label>
<label><input type="checkbox" value="apple" v-model="selectedFruits" />사과</label>
<label><input type="checkbox" value="orange" v-model="selectedFruits" />오렌지</label>
<p>선택한 과일: {{ selectedFruits }}</p>
</template>
- 이 경우 Vue는 자동으로
selectedFruits배열에 체크된 값을 추가하거나 제거합니다.
2) 라디오 버튼 (Radio)
- 라디오는 하나만 선택 가능한 항목
<script>
export default {
data() {
return {
selectedGender: '',
};
},
};
</script>
<template>
<label><input type="radio" value="male" v-model="selectedGender" />남자</label>
<label><input type="radio" value="female" v-model="selectedGender" />여자</label>
<p>선택된 성별: {{ selectedGender }}</p>
</template>
v-model은 선택된 라디오 버튼의value값을selectedGender에 저장합니다.
3) 셀렉트 박스 (Select)
- 드롭다운에서 하나 또는 여러 항목을 선택 가능
① 단일 선택
<script>
export default {
data() {
return {
selectedCountry: '',
};
},
};
</script>
<template>
<select v-model="selectedCountry">
<option disabled value="">국가를 선택하세요</option>
<option value="KR">대한민국</option>
<option value="JP">일본</option>
<option value="US">미국</option>
</select>
<p>선택된 국가: {{ selectedCountry }}</p>
</template>
- 초기에는 빈 값(
'')을 넣어 선택을 강제하지 않도록 합니다. - 사용자가 선택한 국가의
value값이selectedCountry에 바인딩됩니다.
② 다중 선택
<script>
export default {
data() {
return {
selectedColors: [],
};
},
};
</script>
<template>
<select v-model="selectedColors" multiple>
<option value="red">빨강</option>
<option value="green">초록</option>
<option value="blue">파랑</option>
</select>
<p>선택된 색상: {{ selectedColors }}</p>
</template>
multiple속성을 추가하면 배열로 값이 바인딩됩니다.- 사용자가 여러 값을 선택할 수 있고,
selectedColors에 선택된 항목이 자동으로 저장됩니다.
요소별 v-model 정리표
| 요소 | 타입 | 바인딩 타입 | 특징 |
|---|---|---|---|
<input type="text"> |
단일 입력 | 문자열 | 가장 기본적인 사용 |
<textarea> |
여러 줄 입력 | 문자열 | 줄바꿈 포함 |
<input type="checkbox"> |
단일 | boolean | 체크 여부 |
<input type="checkbox"> |
여러 개 | 배열 | value가 배열로 |
<input type="radio"> |
단일 | 문자열 | 하나만 선택 가능 |
<select> |
단일 | 문자열 | 드롭다운 선택 |
<select multiple> |
다중 | 배열 | 여러 항목 선택 가능 |
요약
v-model은 다양한 입력 요소를 상황에 맞게 쉽게 연결할 수 있도록 해주는 매우 강력한 도구입니다.- Vue가 내부적으로 input/change 이벤트와 value 바인딩을 자동 처리해줍니다.
- 체크박스나 셀렉트 같은 요소들은 특별한 구조를 가지므로 이해하고 사용해야 정확히 동작합니다.
3.5.3 폼 요소와 이벤트
1. input 이벤트
input 이벤트는 <input> 입력 요소의 값이 바뀔 때마다 발생합니다. 입력이 실시간으로 반영되며, 특히 v-model이 한글 입력 문제(조합 중인 글자가 바로 반영되지 않는 문제)를 겪을 때 사용하면 좋습니다.
<template>
<input type="text" @input="onChangeHandler($event)" />
{{ message }}
</template>
<script>
export default {
data() {
return {
message: ''
};
},
methods: {
onChangeHandler(event) {
this.message = event.target.value;
}
}
};
</script>
v-model대신@input을 쓰면,event.target.value를 직접 가져와서data에 할당할 수 있음- 한글 입력도 실시간 반영되어 문제 없음
✅ 2. change 이벤트
<select>,<input>,<textarea>등의 값이 바뀌고 포커스를 잃을 때 발생합니다.select박스처럼 선택값에 따라 추가 동작이 필요한 경우 유용합니다.
<template>
<select v-model="selected" @change="onChangeHandler">
<option value="banana">바나나</option>
<option value="apple">사과</option>
</select>
<p>가격: {{ price }}원</p>
</template>
<script>
export default {
data() {
return {
selected: 'banana',
price: 500
};
},
methods: {
onChangeHandler(event) {
if (this.selected === 'banana') {
this.price = 500;
} else if (this.selected === 'apple') {
this.price = 700;
}
}
}
};
</script>
v-model로 선택한 값을 바인딩하고,@change로 선택 값에 따라 다른 동작(가격 변경)을 수행.
3. submit 이벤트
<form>태그에서 데이터 전송 시 발생- 기본 동작은 페이지 이동 → 이를 막기 위해
preventDefault()를 사용함 - 실제로는 AJAX 요청을 보낼 때 많이 활용됩니다
<template>
<form @submit="onSubmitHandler">
<button type="submit">전송</button>
</form>
</template>
<script>
export default {
methods: {
onSubmitHandler(e) {
e.preventDefault();
console.log('onSubmit Handler!');
}
}
};
</script>
@submit은<form>에 붙이며, 폼 전송 이벤트를 잡음e.preventDefault()로 페이지 리로드 막고,console.log()로 동작 확인 가능
4. keyup 이벤트
- 사용자가 키보드를 눌렀다가 뗄 때 발생
- 입력 중인 키를 기반으로 조건별 로직을 수행할 수 있음
<template>
<input type="text" @keyup="onKeyupHandler" />
</template>
<script>
export default {
methods: {
onKeyupHandler() {
console.log('keyup event!');
}
}
};
</script>
- 입력 중 키보드 이벤트를 감지함
console.log()로 확인할 수 있고, 다양한 조합(@keyup.enter,@keyup.esc)도 사용 가능
보충 팁: keypress vs keydown vs keyup
| 이벤트 | 발생 시점 |
|---|---|
| keypress | 키를 누르고 있을 때 |
| keydown | 키를 누를 때 |
| keyup | 키를 뗄 때 |
Vue에서는 보통 keyup을 가장 많이 사용합니다.
3.6 계산된 속성과 감시자 속성
Vue에서는 데이터와 UI가 연결되어 동작합니다. 이 연결을 더 유연하고 효율적으로 하기 위해 두 가지 강력한 도구가 있습니다.
- 계산된 속성 (computed)
- 감시자 속성 (watch)
두 속성은 모두 데이터를 기반으로 다른 데이터를 생성하거나 특정 동작을 수행할 수 있게 해줍니다.
이 중에서 먼저 계산된 속성부터 자세히 살펴볼게요.
3.6.1 계산된 속성 (computed)
computed는 Vue의 옵션 API에서 제공하는 속성입니다.data()의 값을 기반으로 새로운 값을 계산하여 반환합니다.- 중요한 특징은 바로 캐싱입니다.
- 종속된 값이 바뀌지 않는 한, 다시 계산하지 않고 이전 결과를 재사용합니다.
- Vue가 자동으로 종속성을 추적합니다.
예시 1: 계산 없이 일반 렌더링
<template>
<h1>{{ lastName }} {{ firstName }}</h1>
<h1>{{ lastName }} {{ firstName }}</h1>
</template>
<script>
export default {
data() {
return {
firstName: 'Gildong',
lastName: 'Hong',
};
},
};
</script>
firstName,lastName을 직접 두 번 출력하고 있습니다.- 이럴 경우, 템플릿에서는 매번 두 개의 데이터 속성을 따로 읽어야 합니다.
- 이름이 바뀌면 두 곳 모두 렌더링이 다시 일어나죠.
예시 2: computed를 사용한 버전
<template>
<h1>{{ fullName }}</h1>
<h1>{{ fullName }}</h1>
</template>
<script>
export default {
data() {
return {
firstName: 'Gildong',
lastName: 'Hong',
};
},
computed: {
fullName() {
console.log('computed fullname'); // 콘솔에서 확인 가능
return `${this.lastName} ${this.firstName}`;
},
},
};
</script>
fullName이라는 계산된 속성을 만들고, 이를 템플릿에서 두 번 사용합니다.- 콘솔 로그를 확인해보면,
fullName을 처음 계산할 때만console.log가 출력되고, - 이후
firstName이나lastName이 변하지 않으면 재계산되지 않습니다.
즉, computed 속성은:
- 의존하는 데이터가 변할 때만 자동으로 다시 계산됩니다.
- 그렇지 않으면 캐시된 값을 재사용하여 성능을 향상시킵니다.
실용적인 활용 예: 계산이 많은 로직
<template>
<h1>{{ evenSum }}</h1>
</template>
<script>
export default {
data() {
return {
numArr: [1, 2, 3, 4, 5],
};
},
computed: {
evenSum() {
return this.numArr
.filter((v) => v % 2 === 0)
.reduce((acc, cur) => acc + cur, 0);
},
},
};
</script>
numArr에서 짝수만 골라 합산하는 로직입니다.filter()와reduce()를 연달아 쓰면 템플릿에 직접 넣기엔 너무 복잡합니다.- 이런 복잡한 연산을 computed 속성으로 분리하면 템플릿이 훨씬 간결해집니다.
3.6.2 감시자 속성 (watch)
watch는 Vue 옵션 API에서 제공하는 속성입니다.- 데이터 속성의 변화를 감시하고, 변화가 발생할 때마다 특정 동작을 수행하게 합니다.
- 반응형 데이터의 변경을 트리거로 삼아 사이드 이펙트(부가 동작)를 처리할 때 유용합니다.
<template>
<input type="text" v-model="inputStr" />
</template>
<script>
export default {
data() {
return {
inputStr: '',
};
},
watch: {
inputStr(newValue, oldValue) {
console.log(`old value : ${oldValue}`);
console.log(`new value : ${newValue}`);
},
},
};
</script>
inputStr의 값을 감시합니다.- 사용자가 input에 값을 입력할 때마다
watch가 실행됩니다. newValue,oldValue는 각각 새 값과 이전 값을 나타냅니다.- 위 예제는 콘솔에 값의 변화 로그를 출력합니다.
실행 결과는 사용자가 a라고 입력할 경우, 콘솔에는 다음과 같이 출력됩니다.
old value :
new value : a
watch와 computed 차이점 간단 정리
| 항목 | computed | watch |
|---|---|---|
| 사용 목적 | 값 계산과 캐싱 | 값 변경 감지 후 부가 동작 수행 |
| 리턴 값 필요 여부 | 있음 | 없음 (보통 함수 실행) |
| 내부적으로 추적? | 자동 (종속성 추적 및 캐싱) | 수동 (감시할 속성 지정 필요) |
| 비동기 작업 | 부적합 | 적합 (API 호출 등 부수 효과 처리에 유용) |
⚠️ 1. 깊은 감시 (Deep Watch)
깊은 감시가 왜 필요한가요?
- 기본적으로
watch는 반응형 속성의 최상위 레벨만 감지합니다. - 하지만 객체 내부 속성이나 배열 요소가 바뀌는 것을 감지하려면
deep: true옵션을 사용해야 합니다.
예제: 객체와 배열 감시 실패
<script>
export default {
data() {
return {
arr: [0, 1, 2],
obj: { name: 'chulsu' },
};
},
watch: {
arr(newValue, oldValue) {
console.log(`old value: ${oldValue}`);
console.log(`new value: ${newValue}`);
},
obj(newValue, oldValue) {
console.log(`old value: ${JSON.stringify(oldValue)}`);
console.log(`new value: ${JSON.stringify(newValue)}`);
},
},
};
</script>
<template>
<h1>{{ arr }}</h1>
<h1>{{ obj }}</h1>
<button @click="arr.push(3)">배열 변경</button>
<button @click="obj.age = 20">객체 변경</button>
</template>
❌ 문제점
- 버튼을 클릭해도
watch는 변화를 감지하지 못함 - 이유는 배열이나 객체의 내부 속성 변경은 참조값이 같기 때문에 감지 대상에서 제외됨
✅ 해결 방법: deep: true
watch: {
arr: {
handler(newValue, oldValue) {
console.log(`배열 변경 감지됨: ${newValue}`);
},
deep: true,
},
obj: {
handler(newValue, oldValue) {
console.log(`객체 변경 감지됨: ${JSON.stringify(newValue)}`);
},
deep: true,
},
}
deep: true는 내부의 중첩된 속성까지 감시할 수 있게 해줍니다.- 객체 구조가 복잡할수록 꼭 필요한 옵션입니다.
2. watch 옵션 형식 정리
Vue에서 watch 속성은 아래와 같은 구성으로 작성할 수 있습니다.
watch: {
[dataOrComputedKey]: {
handler(newVal, oldVal) { ... },
deep: false,
immediate: false,
flush: 'pre',
once: false
}
}
| 속성 | 설명 |
|---|---|
dataOrComputedKey |
감시할 데이터 또는 계산된 속성 이름 |
handler |
값이 변경될 때 실행할 함수 |
deep |
내부까지 변경을 감지할지 여부 (기본값: false) |
immediate |
처음 렌더링 시에도 실행할지 여부 (기본값: false) |
flush |
실행 타이밍 설정: 'pre', 'post', 'sync' 중 선택 |
once |
최초 한 번만 감지할지 여부 (Vue 3.4+ 지원) |
🧪 실제 변화 확인
<button @click="arr.push(3)">를 클릭하면arr배열에 값이 추가되고 콘솔에 로그가 출력됨<button @click="obj.age = 20">을 클릭하면obj에 새로운 속성이 추가되고 감지됨
출력 예시
배열 변경 감지됨: 0,1,2,3
객체 변경 감지됨: { name: 'chulsu', age: 20 }
3. immediate: true: 처음부터 실행하기
- 일반적으로
watch는 데이터가 변경되었을 때만 실행됩니다. - 하지만 컴포넌트가 처음 생성되었을 때 바로 실행하고 싶은 경우에는
immediate: true를 사용합니다.
<script>
export default {
data() {
return {
inputStr: 'Hello',
};
},
watch: {
inputStr: {
handler(newVal, oldVal) {
console.log(`new: ${newVal}, old: ${oldVal}`);
},
immediate: true,
},
},
};
</script>
<template>
<input v-model="inputStr" />
</template>
- 컴포넌트가 렌더링되자마자 watch의 핸들러가 실행됩니다.
immediate: true는 초기값에 대해서도 반응하도록 도와줍니다.
결과
new: Hello, old: undefined
4. flush: 실행 타이밍 조절
flush는 어떤 문제를 해결하나요?
watch는 기본적으로 DOM 업데이트 이전에 실행됩니다 (flush: 'pre')- DOM이 변경된 이후에 실행하고 싶을 때는
flush: 'post' - 혹은 완전히 동기적으로 바로 실행하고 싶을 때는
flush: 'sync'
watch: {
inputStr: {
handler(newVal) {
console.log('변경 감지됨');
},
flush: 'post', // DOM 업데이트가 끝난 후에 실행
},
}
flush 옵션 요약
| 값 | 의미 |
|---|---|
'pre' (기본값) |
DOM 업데이트 전에 실행 |
'post' |
DOM 업데이트 후 실행 |
'sync' |
반응성 변경이 일어나는 즉시 실행 |
5. once – 한 번만 실행 (Vue 3.4+)
once는 언제 사용하나요?
- 어떤 값의 변화를 단 한 번만 감지하고 싶은 경우 유용합니다.
- 예를 들어 최초 로딩 이후 특정 설정을 한 번만 처리하고 싶을 때 사용합니다.
watch: {
inputStr: {
handler(newVal) {
console.log(`입력: ${newVal}`);
},
once: true,
},
}
- inputStr이 한 번 변경된 후에는 더 이상 감지되지 않습니다.
once옵션은 Vue 3.4부터 지원되므로 버전에 유의하세요.
정리: 고급 옵션들 요약
| 옵션 | 설명 |
|---|---|
deep |
객체나 배열의 내부까지 감시 |
immediate |
컴포넌트 마운트 직후에도 감시자 실행 |
flush |
실행 타이밍 조절 (DOM 업데이트 전/후/즉시) |
once |
값이 한 번 바뀌면 더는 감시하지 않음 |
computed vs watch 비교
| 항목 | computed |
watch |
|---|---|---|
| ❓ 목적 | 계산된 값을 반환 및 캐싱 (출력용) | 값의 변화를 감지해서 작업을 수행 |
| ⚙️ 사용 형태 | 속성처럼 사용 ({{ fullName }}) |
함수처럼 정의 (watch: { val(newVal) { ... }}) |
| 🧠 내부 캐싱 | ✅ 있음 (의존 데이터 변경 전까지는 재계산하지 않음) | ❌ 없음 (매번 실행됨) |
| 🔁 반응 방식 | 의존하는 데이터가 변경될 때만 재평가 | 어떤 값이 바뀔 때 부수 효과(side effect) 발생 |
| 🔧 대표 활용 | 템플릿에서 가공된 데이터 표시 | API 호출, 타이머 설정, 콘솔 로그, 조건 분기 등 |
computed, watch 중 언제 어떤 걸 써야 할까?
| 상황 | 선택 |
|---|---|
| 템플릿에서 보여줄 데이터를 가공하고 싶다 | computed |
| 데이터가 바뀔 때 어떤 동작을 수행해야 한다 (예: API 호출) | watch |
| 여러 데이터에 의존하는 복잡한 연산 결과를 캐싱하고 싶다 | computed |
| 시간 지연, 비동기, 사이드 이펙트 필요 | watch |
| 값이 바뀌었을 때 로그를 찍고 싶다 | watch |
💡 팁: 둘 다 사용 가능할 때는 computed를 우선 고려하세요!
computed는 성능(캐싱) 면에서 유리합니다.- 단, computed는 함수 내부에서 부수 효과를 일으키면 안 됩니다.
- 예: computed 안에서 API 호출 ❌
methods vs computed vs watch 비교 요약
| 항목 | methods |
computed |
watch |
|---|---|---|---|
| 🎯 목적 | 이벤트 핸들링 또는 일반 함수 실행 | 의존 데이터 기반의 계산 결과 반환 | 데이터의 변화 감지 후 부수 작업 수행 |
| 💾 값 캐싱 | ❌ 없음 (매번 실행) | ✅ 있음 (의존 값이 바뀌지 않으면 캐시) | ❌ 없음 (매번 실행) |
| 🧠 사용 대상 | 사용자 입력, 버튼 클릭 등 직접 호출 | 템플릿에서 계산된 값 바인딩 | 값의 변화에 따른 부가 로직 실행 |
| 🔁 자동 실행 | ❌ 수동 실행 | ✅ 자동 실행 | ✅ 자동 실행 |
| 💬 예시 사용 | @click="doSomething" |
{{ fullName }} |
사용자 이름 바뀔 때 API 호출 등 |
**
실전 예제: 사용자 이름 변경 감지**
① methods – 버튼 클릭 시 동작
<template>
<button @click="sayHello">인사하기</button>
</template>
<script>
export default {
methods: {
sayHello() {
alert("안녕하세요!");
},
},
};
</script>
- 사용자 액션(버튼 클릭)에 대한 처리는
methods가 적절합니다.
② computed – 데이터 가공해서 표시
<template>
<h1>{{ fullName }}</h1>
</template>
<script>
export default {
data() {
return {
firstName: '홍',
lastName: '길동',
};
},
computed: {
fullName() {
return `${this.lastName} ${this.firstName}`;
},
},
};
</script>
템플릿에서 보여줄 값을 계산해서 출력하려면 computed를 사용하세요.
**
③ watch – 이름이 바뀌면 API 호출**
<template>
<input v-model="username" />
</template>
<script>
export default {
data() {
return {
username: '',
};
},
watch: {
username(newVal) {
console.log(`API 호출! 사용자명: ${newVal}`);
},
},
};
</script>
- 데이터가 변경될 때마다 어떤 작업을 하고 싶다면
watch를 사용하세요. - 대표적으로
API 호출,콘솔 로그,타이머,로컬스토리지 저장등입니다.
실무에서 자주 쓰는 조합
| 사용 목적 | 구성 예시 |
|---|---|
| 계산된 속성을 템플릿에 표현 | computed |
| 사용자가 값을 입력 → 변화 감지하여 API 호출 | v-model + watch |
| 버튼 클릭 → 특정 액션 실행 | @click + methods |
| 입력값을 기준으로 동적으로 텍스트 표시 | v-model + computed |
결론 정리
- 단순 계산 후 보여주기 →
computed - 이벤트 발생 시 직접 처리 →
methods - 값 변화 감지 후 부가 동작 →
watch
3.7 스타일 다루기
Vue에서 스타일을 적용하는 방법은 HTML과 비슷하지만 조금 더 강력하고 유연하게 스타일을 다룰 수 있게 해줍니다.
스타일 적용 방식은 이전의 CSS 스타일 방식과 동일한 세 가지로 나뉩니다.
| 구분 | 설명 |
|---|---|
| 1. 인라인 스타일 (Inline Style) | 태그의 style 속성에 직접 작성 |
| 2. 내부 스타일 (Internal Style) | <style> 태그를 Vue 컴포넌트 내부에 작성 |
| 3. 외부 스타일 (External Style) | .css 파일로 분리 후 @import 하여 적용 |
3.7.1 인라인 스타일
- HTML에서
<태그 style="...">처럼 직접 스타일 속성을 쓰는 방식과 동일
<template>
<h1 style="color: red; font-style: italic">Inline Style</h1>
</template>
- 스타일을 직접
style="..."로 명시 - 가장 단순하고 즉시 적용 가능
- 하지만 재사용성, 유지보수성은 낮음
v-bind와 함께 사용하기
Vue에서는 v-bind 디렉티브를 통해 동적으로 스타일 속성을 설정할 수 있습니다.
<template>
<h1 :style="{ color: primaryColor, fontStyle: primaryStyle }">Inline Style</h1>
</template>
<script>
export default {
data() {
return {
primaryColor: 'red',
primaryStyle: 'italic'
};
}
}
</script>
:style="{ ... }"는 객체 형태로 여러 스타일 속성을 바인딩color,fontStyle같은 속성명을 camelCase로 작성해야 함- HTML의
font-style→ JS에서는fontStyle
결과 화면

- 두 예제 모두 결과는
<h1>태그에 빨간색, 이탤릭체 텍스트로"Inline Style"이 출력됨 - 단순 스타일 지정은 첫 번째 방법으로 충분하지만,
- 동적 스타일링이 필요할 땐 두 번째 방식처럼 v-bind를 활용한 객체 바인딩이 유용함
3.7.2 내부 스타일 (Internal Style)
- 내부 스타일은 Vue 컴포넌트 내부의
<style>태그 안에 작성하는 방식 - 이 방식은 한 컴포넌트에서만 사용하는 스타일을 정의할 때 자주 사용
<template>
<h1>Internal Style</h1>
</template>
<style>
h1 {
color: red;
font-style: italic;
}
</style>

- 결과: 위 코드를 실행하면
<h1>텍스트가 빨간색이고 이탤릭체로 표시됩니다.
주의점은 기본적으로 이 <style> 태그에 작성된 CSS는 해당 파일에 포함된 모든 컴포넌트에 전역으로 적용됩니다.
🔐 scoped 속성
✅ 컴포넌트에만 스타일을 제한하고 싶을 때
<style scoped>
h1 {
color: red;
font-style: italic;
}
</style>
scoped 속성을 추가하면 이 스타일은 해당 컴포넌트 내부에만 영향을 미칩니다.
scoped 배경 설명:
- Vue는 내부적으로 고유한 data attribute(예:
data-v-xxxxx)를 자동 생성해서 해당 컴포넌트의 요소에만 스타일이 적용되도록 만들어줍니다. - 따라서 다른 컴포넌트의
<h1>에는 전혀 영향을 주지 않습니다.
3.7.3 외부 스타일 (External Style)
- 외부 스타일은 별도의
.css파일을 만들어 놓고 Vue 컴포넌트에서@import문으로 참조하여 적용하는 방식
✅ 사용 예시 1: 상대 경로로 가져오기
<template>
<h1>External Style</h1>
</template>
<style>
@import './assets/main.css';
</style>
위 예제에서는 src/assets/main.css 파일에 작성된 스타일이 적용됩니다.
✅ 사용 예시 2: vite.config.js의 alias 경로 사용
// vite.config.js
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
'~': fileURLToPath(new URL('./src/assets', import.meta.url))
}
}
})
이렇게 설정하면 @ 또는 ~를 사용해 더욱 간결하게 CSS를 import할 수 있습니다.
<style>
@import '~/main.css';
</style>
- 이 방식은 경로 길이를 줄이고, 재사용성과 유지보수를 향상시킵니다.
세 가지 스타일 방식 요약 비교
| 구분 | 설명 | 재사용성 | 유지보수 | 범위 |
|---|---|---|---|---|
| 인라인 스타일 | 태그에 직접 style="" |
낮음 | 낮음 | 태그 1개 |
| 내부 스타일 | <style> 태그 사용 |
보통 | 보통 | 기본: 전역 / scoped: 컴포넌트만 |
| 외부 스타일 | .css 파일 분리 후 @import |
높음 | 높음 | 다수 컴포넌트 공유 가능 |
'개인 정리w' 카테고리의 다른 글
| 리프레시 토큰 조회 성능 테스트(k6, Postgres, Redis) (0) | 2025.03.29 |
|---|---|
| String 클래스 총정리(feat. StringBuilder, StringBuffer, Arrays) (2) | 2025.01.11 |
| JDK, JRE, JVM 총정리(feat. 자바 컴파일 과정) (2) | 2024.12.26 |
| 자바(Java): 자바 프로그램의 실행 과정(feat. 컴파일 타임 환경, 런타임 환경, JVM) (1) | 2024.09.09 |
댓글