Vue에서 <template>을 작성하다 보면 일반 HTML에서는 볼 수 없었던 속성들이 등장한다.
v-html
v-bind
v-if
v-show
v-for
이처럼 v-로 시작하는 Vue 전용 특수 속성을 Directive(디렉티브)라고 한다.
Directive를 사용하면 HTML 태그와 JavaScript 데이터를 연결하여 화면의 내용, 속성, 스타일, 조건, 반복 등을 동적으로 제어할 수 있다.
Vue의 Directive에서 특히 중요한 점은 다음과 같은 코드에서
<img :src="imageUrl">
"imageUrl"이 단순한 문자열이 아니라 JavaScript 표현식이 실행되는 영역이라는 것이다.
즉 Vue의 Template은 단순한 HTML이 아니라 JavaScript 데이터와 연결되어 동적으로 변경되는 화면이라고 볼 수 있다.
데이터를 화면에 출력하는 방법
Vue에서 데이터를 화면에 출력하는 가장 기본적인 방법은 {{ }}를 사용하는 것이다.
<script setup>
const message = 'Hello Vue'
</script>
<template>
<p>{{ message }}</p>
</template>
화면에는 다음과 같이 출력된다.
Hello Vue
이를 Text Interpolation(텍스트 보간법)이라고 한다.
하지만 문자열 안에 HTML 코드가 들어 있다면 상황이 조금 달라진다.
const message = '<strong>Hello Vue</strong>'
일반 보간법으로 출력하면
<p>{{ message }}</p>
화면에는 HTML이 적용되는 것이 아니라 문자열 그대로 출력된다.
<strong>Hello Vue</strong>
v-html
문자열을 실제 HTML Element로 해석해서 출력하고 싶다면 v-html을 사용할 수 있다.
<script setup>
const rawHtmlData =
'이 글자는 <span style="color:red; font-weight:bold;">빨간색 굵은 글자</span>이다.'
</script>
<template>
<p v-html="rawHtmlData"></p>
</template>
v-html은 JavaScript의 innerHTML과 유사하게 동작한다.
개념적으로 보면 다음과 같다.
{{ data }}
↓
문자열 그대로 출력
v-html="data"
↓
HTML로 해석하여 출력
다만 v-html을 사용할 때는 XSS(Cross-Site Scripting)에 주의해야 한다.
사용자가 입력한 값을 그대로 v-html에 넣으면 해당 문자열이 HTML로 해석되기 때문에 악의적인 코드가 브라우저에서 실행될 위험이 있다.
게시판이나 입력창에 악성 코드가 삽입되고, 다른 사용자가 이를 조회할 때 실행되는 상황을 XSS의 대표적인 위험이다.
따라서 일반적으로는
{{ data }}
를 사용하고, 신뢰할 수 있는 HTML을 반드시 렌더링해야 할 때만 v-html을 사용하는 것이 좋다.
v-text
v-text도 데이터를 텍스트로 출력하는 Directive다.
<script setup>
const content = '안녕하세요! Vue 강의입니다.'
</script>
<template>
<p v-text="content"></p>
</template>
이는 일반 보간법과 거의 같은 역할을 한다.
<p>{{ content }}</p>
자료에서도 v-text는 텍스트 내용을 채워 넣으며, 일반적인 경우에는 v-text보다 {{ }}를 주로 사용하는 방식으로 설명하고 있다.
따라서 세 가지를 비교하면 다음과 같다.
| 문법 | 역할 |
| {{ data }} | 데이터를 텍스트로 출력 |
| v-text="data" | 데이터를 텍스트로 출력 |
| v-html="data" | 데이터를 HTML로 해석하여 출력 |
HTML 속성과 Vue 데이터를 연결하는 v-bind
Vue에서 가장 많이 사용하는 Directive 중 하나가 v-bind다.
v-bind는 HTML의 Attribute와 JavaScript 데이터를 동적으로 연결하는 기능이다.
예를 들어 일반 HTML에서는 이미지 주소를 다음과 같이 고정한다.
<img src="/images/logo.png">
Vue에서는 이미지 주소를 변수로 관리할 수 있다.
const logoImgSrc = '<a href=https://vuejs.org/images/logo.png'>https://vuejs.org/images/logo.png'</a>
Vue에서 v-bind는 매우 자주 사용되기 때문에 일반적으로 다음과 같이 :로 축약한다.
<img :src="logoImgSrc">
즉
v-bind:src="logoImgSrc"
와
:src="logoImgSrc"
는 같은 의미다.
href, src, disabled 등 다양한 HTML 속성과 연결할 수 있다.
<a :href="dynamicUrl">사이트 이동</a>
<img :src="logoImgSrc">
<button :disabled="isButtonDisabled">
버튼
</button>
전체적인 구조는 다음과 같이 이해하면 된다.
JavaScript 데이터
↓
v-bind
↓
HTML Attribute
class도 동적으로 변경할 수 있다
v-bind는 단순히 src, href와 같은 속성만 연결하는 것이 아니다.
CSS 클래스도 데이터 상태에 따라 동적으로 추가하거나 제거할 수 있다.
가장 대표적인 방법은 객체 형태다.
<p :class="{ 'text-danger': isWarning }">
경고 메시지
</p>
isWarning이
true
이면 text-danger 클래스가 적용되고,
false
이면 클래스가 제거된다.
즉 다음과 같다.
isWarning = true
↓
class="text-danger"
isWarning = false
↓
클래스 제거
Vue에서는 여러 개의 클래스를 배열 형태로 조합하는 것도 가능하다.
<div
:class="[
themeClass,
isWarning ? 'border-red' : 'border-gray'
]"
>
내용
</div>
:class가 객체와 배열 형식을 지원하며, 객체에서는 Boolean 조건으로 클래스 포함 여부를 정하고 배열에서는 여러 클래스를 조합할 수 있다.
style도 동적으로 변경할 수 있다
인라인 스타일 역시 v-bind로 연결할 수 있다.
<script setup>
const textColor = 'purple'
const fontSize = 20
</script>
<template>
<p :style="{
color: textColor,
fontSize: fontSize + 'px'
}">
Hello Vue
</p>
</template>
Vue에서는 style 객체의 CSS 속성명을 일반적으로 camelCase로 작성한다.
backgroundColor
fontSize
fontWeight
여러 스타일 객체를 합칠 수도 있다.
<div :style="[baseStyle, borderStyle]">
Box
</div>
:style은 객체와 배열을 지원하며, 실시간으로 색상이나 크기 같은 CSS 값을 변경할 때 사용할 수 있다.
다만 대부분의 일반적인 디자인 변경은 :style보다 :class를 사용하는 것이 관리하기 편하다.
:class
→ 미리 만들어놓은 CSS 디자인을 조건에 따라 적용
:style
→ 색상, 크기 등의 값을 직접 동적으로 변경
예를 들어 다크모드나 경고 상태처럼 이미 정의한 디자인을 전환할 때는 :class가 적합하고, 프로그레스바 너비처럼 숫자가 계속 변하는 경우에는 :style이 적합하다.
Vue 3.4의 v-bind 축약 문법
Vue 3.4부터는 HTML 속성명과 JavaScript 변수명이 같다면 더 짧게 작성할 수도 있다.
기존에는
<img :src="src">
라고 작성했다면,
<img :src>
처럼 사용할 수 있다.
예를 들어
const id = 'user-profile-card'
const src = '<a href=https://vuejs.org/images/logo.png'>https://vuejs.org/images/logo.png'</a>
과 같이 작성한다.
즉
:src="src"
에서 이름이 동일하기 때문에
:src
만 남긴 것이다.
조건에 따라 화면을 바꾸는 v-if와 v-show
웹 화면에서는 로그인 여부나 데이터 상태에 따라 특정 요소를 보여주거나 숨겨야 하는 경우가 많다.
Vue에서는 이를 위해 v-if를 사용한다.
<script setup>
import { ref } from 'vue'
const isLogged = ref(false)
</script>
<template>
<p v-if="isLogged">
환영합니다.
</p>
<p v-else>
로그인이 필요합니다.
</p>
</template>
isLogged 값에 따라서 두 요소 중 하나가 렌더링된다.
isLogged = true
→ 환영합니다.
isLogged = false
→ 로그인이 필요합니다.
JavaScript의 if / else if / else처럼 여러 조건도 사용할 수 있다.
<div v-if="score >= 90">
A 학점
</div>
<div v-else-if="score >= 80">
B 학점
</div>
<div v-else-if="score >= 70">
C 학점
</div>
<div v-else>
F 학점
</div>
v-if, v-else-if, v-else는 조건에 따라 실제 DOM 요소를 생성하거나 제거하는 방식으로 동작한다.
v-show는 무엇이 다를까
v-show도 조건에 따라 화면을 보여주거나 숨긴다.
<div v-show="isVisible">
화면에 표시할 영역
</div>
그러나 v-if와 동작 방식이 다르다.
v-if는 조건이 false이면 실제 DOM에서 요소를 제거한다.
v-if = false
DOM에서 제거
반면 v-show는 DOM 요소를 그대로 유지한 상태에서 CSS를 사용한다.
display: none;
즉
v-show = false
DOM에는 존재
↓
display: none
↓
화면에서만 숨김
이다.
두 방식을 비교하면 다음과 같다.
| 구분 | v-if | v-show |
| false일 때 | DOM에서 제거 | DOM 유지 |
| 동작 방식 | DOM 생성/삭제 | display 변경 |
| 초기 렌더링 비용 | 낮음 | 상대적으로 높음 |
| 반복적인 화면 전환 | 상대적으로 부담 | 유리 |
| v-else 사용 | 가능 | 불가능 |
따라서 화면이 거의 변경되지 않는 경우라면 v-if를,
로그인 권한
관리자 메뉴
특정 데이터 존재 여부
처럼 사용할 수 있다.
반대로 자주 열고 닫는 UI라면 v-show가 적합하다.
Modal
Tab
Accordion
Dropdown
이와 같은 기준으로 v-if와 v-show를 구분하고 있다.
반복되는 화면을 만드는 v-for
웹에서는 같은 형태의 데이터를 반복해서 출력하는 경우가 매우 많다.
예를 들어 과일 목록이 있다고 해보자.
const fruits = ['사과', '바나나', '딸기']
HTML을 직접 작성한다면
<li>사과</li>
<li>바나나</li>
<li>딸기</li>
처럼 각각 만들어야 한다.
Vue에서는 v-for를 사용할 수 있다.
<li
v-for="fruit in fruits"
:key="fruit"
>
{{ fruit }}
</li>
배열의 index도 함께 받을 수 있다.
<li
v-for="(fruit, index) in fruits"
:key="index"
>
{{ index + 1 }}번 과일: {{ fruit }}
</li>
구조를 보면
fruits
['사과', '바나나', '딸기']
↓
v-for
↓
<li>사과</li>
<li>바나나</li>
<li>딸기</li>
가 된다.
객체 역시 반복할 수 있다.
const user = {
name: '홍길동',
age: 25,
role: '개발자'
}
<li
v-for="(value, key) in user"
:key="key"
>
{{ key }} : {{ value }}
</li>
그리고 실제 개발에서 가장 자주 만나는 형태는 객체를 가진 배열이다.
const items = [
{ id: 'prod_101', name: '아이폰' },
{ id: 'prod_102', name: '갤럭시' }
]
<li
v-for="item in items"
:key="item.id"
>
{{ item.name }}
</li>
v-for를 사용할 때 Vue가 각각의 요소를 고유하게 구분할 수 있도록 :key를 함께 지정하는 것을 중요한 규칙으로 설명한다.
따라서 일반적으로
v-for="item in items"
:key="item.id"
를 하나의 세트처럼 기억하는 것이 좋다.
특히 고유한 id가 있다면 index보다 해당 id를 key로 사용하는 방식이 적절하다.
알아두면 좋은 기타 Directive
앞에서 본 Directive들은 Vue 개발에서 매우 자주 사용한다.
반면 다음 Directive들은 사용 빈도는 상대적으로 낮지만 Vue의 렌더링 원리를 이해하는 데 도움이 된다.
v-pre
v-pre는 Vue에게
이 영역은 Vue 문법으로 해석하지 말고 그대로 보여줘.
라고 알려주는 Directive다.
<script setup>
const message = '안녕하세요!'
</script>
<template>
<p>{{ message }}</p>
<p v-pre>
{{ message }}
</p>
</template>
첫 번째는
안녕하세요!
가 출력되지만,
v-pre가 적용된 두 번째는
{{ message }}
가 그대로 표시된다.
Vue의 Template Compiler가 해당 영역을 해석하지 않는 것이다.
v-cloak
Vue가 완전히 로딩되기 전에 화면이 먼저 나타나면 아주 잠깐
{{ message }}
같은 미완성 템플릿 문법이 사용자에게 보일 수 있다.
이를 방지하기 위한 Directive가 v-cloak이다.
<div v-cloak>
{{ message }}
</div>
그리고 CSS와 함께 사용한다.
[v-cloak] {
display: none !important;
}
Vue의 렌더링이 끝날 때까지 화면을 숨겼다가, 준비가 완료되면 표시하는 방식이다.
v-once
Vue의 반응형 데이터는 값이 변경되면 화면도 다시 갱신된다.
하지만 한 번 출력된 후 절대 변경할 필요가 없는 데이터도 있을 수 있다.
이럴 때 v-once를 사용할 수 있다.
<p v-once>
{{ count }}
</p>
count가 처음에 1이었다면 이 영역은 최초 렌더링 이후 count가 변경돼도 계속 1을 유지한다.
최초 count = 1
↓
v-once 렌더링
↓
화면 : 1
↓
count = 2
↓
화면 : 여전히 1
즉 최초 한 번만 렌더링하고 이후의 반응형 업데이트를 받지 않는다.
v-memo
v-memo는 조금 더 선택적인 렌더링 최적화 기능이다.
<div v-memo="[name]">
<p>이름: {{ name }}</p>
<p>나이: {{ age }}</p>
</div>
여기서는 name을 감시 대상으로 지정했다.
name 변경
↓
해당 영역 다시 렌더링
age만 변경
↓
기존 렌더링 결과 재사용
즉 지정된 값이 변경됐을 때만 해당 영역을 업데이트하고, 그렇지 않으면 이전에 렌더링한 결과를 재사용한다.
일반적인 작은 화면에서는 자주 사용할 필요가 없지만, 렌더링 비용이 큰 UI를 최적화해야 할 때 사용할 수 있는 Directive다.
Vue Directive 전체 흐름 정리
이번에 살펴본 Directive를 역할별로 묶으면 생각보다 간단하다.
Vue Directive
│
├─ 콘텐츠 출력
│ ├─ v-html
│ └─ v-text
│
├─ 데이터 연결
│ └─ v-bind (:)
│ ├─ Attribute
│ ├─ Class
│ └─ Style
│
├─ 조건
│ ├─ v-if
│ └─ v-show
│
├─ 반복
│ └─ v-for
│
└─ 렌더링 제어
├─ v-pre
├─ v-cloak
├─ v-once
└─ v-memo
이 중 처음 Vue를 공부할 때 가장 중요하게 볼 것은 다음 정도다.
{{ }}
→ 화면에 데이터 출력
:
→ v-bind
→ HTML 속성에 데이터 연결
v-if
→ 조건에 따라 DOM 생성/삭제
v-show
→ 조건에 따라 표시/숨김
v-for
→ 배열이나 객체 반복 출력
그리고 다음 단계에서 여기에
@click
v-model
이 추가된다.
그러면 Vue Template의 기본적인 화면 제어 문법은 거의 완성된다.
실제로 Vue 코드를 보면 다음과 같은 형태가 매우 자주 등장한다.
<script setup>
import { ref } from 'vue'
const isLoggedIn = ref(true)
const users = ref([
{ id: 1, name: 'Kim' },
{ id: 2, name: 'Lee' },
{ id: 3, name: 'Park' }
])
</script>
<template>
<div v-if="isLoggedIn">
<h2>사용자 목록</h2>
<ul>
<li
v-for="user in users"
:key="user.id"
>
{{ user.name }}
</li>
</ul>
</div>
<p v-else>
로그인이 필요합니다.
</p>
</template>
처음에는 여러 문법이 섞여 있어 복잡해 보이지만 하나씩 해석하면 단순하다.
v-if
→ 로그인 상태 확인
v-for
→ users 반복
:key
→ 각각의 사용자 식별
{{ user.name }}
→ 사용자 이름 출력
결국 Vue의 Template 문법은 JavaScript 데이터와 HTML 화면을 연결하기 위한 문법이라고 볼 수 있다.
Vue에서는 개발자가 Real DOM을 직접 찾아서 수정하기보다,
데이터를 선언하고
↓
Directive로 HTML과 연결하고
↓
데이터를 변경하면
↓
Vue가 화면을 갱신한다
라는 흐름으로 개발하게 된다.
'개발 > JavaScript' 카테고리의 다른 글
| [JavaScript][Vue.js] Props와 Emits 정리 - 부모·자식 컴포넌트 통신 (0) | 2026.08.26 |
|---|---|
| [JavaScript][Vue.js] Component Lifecycle과 Lifecycle Hook 정리 (0) | 2026.08.26 |
| [JavaScript][Vue.js] Composition API란? - Options API와의 차이부터 ref, computed, watch까지 (0) | 2026.08.26 |
| [JavaScript][Vue.js] Vue 기본 개념 이해하기 (0) | 2026.08.24 |
| [JavaScript] 비동기 처리 이해하기 - Callback부터 Promise, async/await까지 (0) | 2026.08.24 |