치춘짱베리굿나이스

Vue를 먹자 2 - template 본문

ClientSide/Vue

Vue를 먹자 2 - template

치춘 2023. 10. 20. 14:38

Vue를 먹자

.vue 파일 구성

// App.vue
<template>
    <div class="wrapper">
        <span>{{ str }}</span>
    </div>
</template>

<script setup lang="ts">
const str = 'Hello World!!'
</script>

<style scoped>
.wrapper {
    width: 100vw;
    height: 100vh;
    ...
}
</style>

이전 글에서 Vue 구성을 간단하게 알아보았다

template

Vue에서 화면에 그려지는 요소를 다루는 영역이다

html 파일 작성하듯이 작성하면 되고 (문법이나 컨벤션 등이 html과 일치), 쪼끔쪼끔씩 Vue 문법들이 섞여들어간다

많이 쓰이는 Vue 문법들을 살펴보자

변수 출력하기

<div>
    <span>{{ variableName }}</span>
</div>

콧수염 괄호라고도 불리며, 데이터를 직접 연결 (바인딩) 해줄 때 (변수, 함수의 결과값 등) 사용한다

위의 경우 variableName 이라는 변수를 span 태그 안에 출력해주는 동작이다

 

<div>
    <span>{{ new Date().toDateString() }}</span>
</div>

당연하게도 어떠한 함수의 결과값도 전달할 수 있다

 

<div>
    <span>{{ `${variable1}입니다요~~` }}</span>
</div>
<div>
    <span>{{ variable1 + "입니다요~~" }}</span>
</div>
<div>
    <span>{{ variable1 }} 입니다요~~</span>
</div>

템플릿 리터럴이나 간단한 연산 (반환값이 도출되는 연산들) 도 출력할 수 있다

template

<template>
    <div>
        <span>대충 어떤 요소</span>
    </div>
    <template> <!-- 여기 !! -->
        <div>
            <span>대충 또다른 요소</span>
        </div>
    </template>
</template>

template 영역 내에 template 태그를 중첩해서 쓸 수 있다

리액트에서의 <Fragment> 와 동일하게, 실제 DOM에는 출력되지 않으나 여러 Vue 문법을 적용할 수 있는 html 조각이다

v-forv-if 는 동시에 사용될 수 없으므로 template 로 감싸서 v-if 를 적용한다던지, 반복적으로 특정 요소를 렌더링하되 해당 요소의 부모를 두고 싶지 않을 때 등 다양한 경우에 사용한다

slot

// TestTemplate.vue
<div class="wrapper">
    <slot />
</div>
// TestView.vue
<TestTemplate>
    <div>
        <span>hihi</span>
    <div>
</TestTemplate>

// 아래와 같다
<div class="wrapper">
    <div> <!-- 슬롯에 끼워넣어진 부분 -->
        <span>hihi</span>
    </div>
</div>

자식 컴포넌트를 넘겨받고 싶을 때 쓰는 태그이다

슬롯을 사용하면 템플릿 컴포넌트를 만들어 확장이 유연하게끔 할 수 있다

 

// TestTemplate.vue
<div class="wrapper">
    <slot name="first" />
    <span>and</span>
    <slot name="second" />
</div>
// TestView.vue
<TestTemplate>
    <template v-slot:first>
        <span>hihi</span>
    </template>
    <template v-slot:second>
        <span>byebye</span>
    </template>
</TestTemplate>

// 아래와 같다
<div class="wrapper">
    <span>hihi</span>
    <span>and</span>
    <span>byebye</span>
</div>

여러 개의 슬롯을 부착하고 싶다면 name 속성을 이용하여 슬롯에 이름을 붙여줄 수 있다

컴포넌트에 다른 컴포넌트를 넘겨줄 땐 template로 감싼 뒤 후술할 v-slot 디렉티브를 사용하여 어느 컴포넌트가 어떤 슬롯에 부착될 지 명시한다

 

// TestTemplate.vue
<div class="wrapper">
    <slot name="first">
        <span>No Component attached!!</span>
    </slot>
    <span>and</span>
    <slot name="second">
        <span>No Component attached!!</span>
    </slot>
</div>
// TestView.vue
<TestTemplate>
</TestTemplate>

// 아래와 같다
<div class="wrapper">
    <span>No Component attached!!</span>
    <span>and</span>
    <span>No Component attached!!</span>
</div>

슬롯에 어떠한 자식 컴포넌트도 넘겨주지 않았을 때, 기본으로 렌더링할 요소를 설정할 수도 있다

slot 태그 안에 기본값 요소를 넣어두자

Vue Directives

<button v-on:click="() => { console.log('hihi') }">
    <span>Click Me!!!</span>
</button>

(v-on:clickonClick과 같다는 것만 알아두자)

Vue Directive 란? HTML 태그 내에서 사용될, v- 로 시작하는 모든 속성들을 말한다

<태그 v-[vue 문법]=”자바스크립트 식”></태그> 이런 식으로, 큰따옴표 안에 작성한다

코드 내에서 따옴표가 필요할 경우, 작은 따옴표 () 또는 백틱 (```) 으로 작성한다

변수를 넘겨줄 수도 있고, 위처럼 함수를 바인딩하거나 직접 호출할 수도 있는 등 여러가지 값을 넘겨줄 수 있지만, forif 등의 제어문은 입력할 수 없음에 유의하자

리액트에서 JSX 내의 {} 와 같은 역할을 한다고 생각하면 된다

v-bind

<div>
    <span v-bind:class="variable1">안녕하시오</span>
    <span v-bind:id="variable2">반갑소</span>
</div>

가장 많이 쓰이게 될 문법 중 하나다

HTML 속성 (attribute) 또는 컴포넌트 프로퍼티에 동적인 데이터 값을 바인딩해주고 싶을 때 사용하는 문법이다

 

<div>
    <span :class="variable1">안녕하시오</span>
    <span :id="variable2">반갑소</span>
</div>

정말 많이 쓰이는 문법인 만큼 :[attribute명] 하나로 간소화할 수도 있다

예를 들어 :classv-bind:class 와 같다

: 이 얇아 읽기 힘들다면 v-bind: 로 풀네임을 적어주자

 

// JSX
<div>
    <span className={variable1}>안녕하시오</span>
    <span id={variable2}>안녕하시오</span>
</div>

위의 v-bind 예제를 React JSX 문법으로 표현하면 이와 같다

v-for

<ul>
    <li v-for="i in 5">{{ i }}</li>
</ul>

// 아래와 같다.
<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
</ul>

v-for=”[데이터] in [반복가능한 데이터 뭉치]" 형태의 문법을 사용하며, 지정한 반복문만큼 돌면서 요소를 출력한다

[데이터] 에는 데이터 뭉치 내의 각각의 원소들이 들어간다

  • [반복가능한 데이터 뭉치] 에 숫자가 들어올 경우, 1부터 해당 숫자까지 반복한다
    • [데이터] 에는 1부터 해당 숫자까지의 값이 차례로 들어간다
  • [반복가능한 데이터 뭉치] 에 문자열이 들어올 경우, 문자열의 모든 문자에 대해 반복한다
    • [데이터] 에는 첫 번째 문자부터 마지막 문자까지의 값이 차례로 들어간다
  • [반복가능한 데이터 뭉치] 에 배열이 들어올 경우, 배열의 모든 원소에 대해 반복한다
    • [데이터] 에는 배열의 첫 번째 원소부터 마지막 원소까지가 차례로 들어간다
  • [반복가능한 데이터 뭉치] 에 객체가 들어올 경우, 객체의 모든 하위 속성에 대해 반복한다
    • [데이터] 에는 객체의 첫 번째 속성부터 마지막 속성까지가 차례로 들어간다

 

<ul>
    <li v-for="(i, index) in [2, 4, 6, 8, 10]">{{ index }}: {{ i }}</li>
</ul>

// 아래와 같다.
<ul>
    <li>0: 2</li>
    <li>1: 4</li>
    <li>2: 6</li>
    <li>3: 8</li>
    <li>4: 10</li>
</ul>

반복문의 두 번째 인자로 인덱스를 받아올 수 있다

인덱스는 0부터 시작한다

 

/*
const obj = {
    name: 'chichoon',
    blog: 'blog.chichoon.com',
    bio: 'hello'
}
*/
<ul>
    <li v-for="(i, name, index) in obj">{{ index }}: [{{ name }}] => {{ i }}</li>
</ul>

// 아래와 같다.
<ul>
    <li>0: [name] => chichoon</li>
    <li>1: [blog] => blog.chichoon.com</li>
    <li>2: [bio] => hello</li>
</ul>

반복할 대상으로 객체를 넣어줄 경우, 두 번째 인자로 key를, 세 번째 인자로 index를 받아올 수 있다

v-if, v-else-if, v-else

<div v-if="isLightMode">
    <span>Light Mode!!!</span>
</div>
<div v-else-if="isGrayscaleMode">
    <span>Grayscale Mode!!!</span>
</div>
<div v-else>
    <span>Default Mode!!!</span>
</div>

템플릿 내에서 조건문을 사용하여 특정 요소를 출력하거나 출력하지 않도록 결정할 수 있다

v-if=”조건문” 형태로 사용한다

  • v-else-if 를 같은 형태로 사용하여 v-if에 대한 else if 조건을 추가할 수 있다
  • v-else 를 이용하여 (인자는 받지 않는다) v-if에 대한 else 조건을 추가할 수 있다

위의 경우,

  • isLightModetrue일 때 Light Mode!!! 출력
  • isLightModefalse이면서 isGrayscaleModetrue일 때 Grayscale Mode!!! 출력
  • isLightMode, isGrayscaleMode 가 둘 다 false일 때 Default Mode!!! 출력

 

<template v-if="isLightMode">
    <div>
        <span>첫 번째 요소</span>
    </div>
    <div>
        <span>두 번째 요소</span>
    </div>
</template>

한번에 연속된 여러 요소를 조건부 렌더링 하고 싶다면, 요소 각각에 v-if 를 줄 필요 없이 위에 서술한 template 태그를 활용하여 요소를 묶어줄 수 있다

어차피 template 태그는 DOM 상에 출력되지 않으므로 요소를 묶는 역할로만 사용할 수 있다

v-show

<div v-show="isLightMode">
    <span>Light Mode!!!</span>
</div>

v-if 와 비슷한 디렉티브

아예 DOM 트리 상에서 없애는 v-if와 다르게 v-show 는 css적으로 display: none 속성만 토글해서 요소를 숨긴다

DOM을 직접 조작해서 무거운 v-if보다 CSS만 건드리는 v-show를 사용하는 것이 적절할 때도 꽤 있다

v-text

<span v-text="'hello world'"></span>

// 아래와 같다
<span>hello world</span>

.innerText와 같은 역할을 하며, 태그 내에 문자열을 넣어줄 수 있다

v-html

<div v-html="<span>hello world</span>"></div>

// 아래와 같다
<div>
    <span>hello world</span>
</div>

.innerHTML과 같은 역할을 하며, 태그 내에 html을 넣어줄 수 있다

React에서 같은 역할을 하는 프로퍼티명이 dangerouslySetInnerHTML 이었던 것을 상기시키며…, v-html도 비슷한 단점 (XSS 공격에 취약) 이 있으므로 조심히 사용하자

v-on

<button v-on:click="console.log('hihi')">
    <span>버튼 클릭!!</span>
</button>

v-on:[이벤트명]=”[이벤트 발생 시 실행할 동작” 형태로 사용한다

React의 onClick, onFocus, onBlur, onChange, … 와 같은 역할을 하는, 이벤트 리스너를 부착하는 디렉티브이다

 

<form v-on:submit.prevent="handleSubmit">
    ...
</form>

필요하다면 Modifier 를 붙여 특별한 설정을 할 수도 있다

가장 많이 사용되는 것은 preventDefault() 설정을 대신 해 주는 .prevent 인듯,,,

 

// template
<button v-on:click="handleClick">
    <span>버튼 클릭!!</span>
</button>

// script
function handleOnClick(e: Event) {
    console.log("hihi");
}

이벤트 핸들러를 스크립트 부분에서 선언하여 부착할 수도 있다

인자로 event를 받아올 수 있다

 

<form @submit.prevent="handleSubmit">
    ...
</form>

v-bind 와 마찬가지로 많이 쓰이는 문법인 만큼 v-on을 생략하고 @으로 대체가 가능하다

v-model

<input v-model="inputValue" />

// script
const inputValue = ref(''); // ref 는 다음에 다뤄보도록 하자

양방향 데이터 바인딩을 지원하는 문법이다

원래 v-bind (부모 → 자식 또는 ViewModel → View, props와 같은 역할), v-on (자식 → 부모 또는 View → ViewModel, 특정한 이벤트 발생) 을 같이 사용해야 양방향 데이터 바인딩이 완성되는데, 이를 더 쉽게 사용할 수 있게끔 두 디렉티브의 기능을 합친 것이다

기본적으로 input 이벤트를 listen 하며, Modifier 로 .lazy 를 붙이면 input 이벤트 대신 change 이벤트를 listen 하게 된다

input, selecttextarea 3개의 태그에서만 지원하며, 사용자의 입력값을 받는다는 공통점이 있다

위의 예제의 경우, inputValue 값이 화면상에서 (사용자의 조작에 의해) 변경되면 script 상에서의 inputValue 변수의 값도 같이 바뀌는 것이다

단점으로 한국어, 일본어, 중국어 입력이 약간 메롱하다는 점이 있다 (이때는 v-bind, v-on을 조합해서 써야 한다)

 

// CustomInput.vue => template
<input 
    :value="value" 
    @input="$emit('update:value', ($event.target as HTMLInputElement).value"
/>

// CustomInput.vue => script
defineProps(['value']);
defineEmits(['update:value']);
// Page.vue => template
<CustomInput v-model:value="test" />

// Page.vue => script
const test = ref('');

커스텀 컴포넌트에 v-model 을 사용하고 싶다면 이와 같이 사용한다

  1. input 에 직접 v-model 을 적용할 경우 valueprop을 직접 변경하려고 시도하므로 (Vue에서 prop은 read-only로 처리된다) input 에 직접 v-bind, v-on 을 부착해 주어야 한다
  2. input 태그에서의 event 이름은 update:[변수명] 으로 지정한다
  3. CustomInput 에는 v-model을 부착하여 처리하면 된다
    • v-model:[변수명] 으로 해당 변수에 양방향 바인딩을 걸어줄 수 있다

 

// CustomInput.vue => template
<input
  class="form-control form-control-lg"
  :value="value1"
  @input="$emit('update:value1', ($event.target as HTMLInputElement).value)"
/>
<input
  class="form-control form-control-lg"
  :value="value2"
  @input="$emit('update:value2', ($event.target as HTMLInputElement).value)"
/>

// CustomInput.vue => script
defineProps(['value1, value2']);
defineEmits(['update:value1', 'update:value2']);
// Page.vue => template
<CustomInput v-model:value1="input1" v-model:value2="input2" />
// value1 에 input1을, value2 에 input2를 각각 연결

// Page.vue => script
const input1 = ref('');
const input2 = ref('');

커스텀 컴포넌트에 2개 이상의 v-model을 정의하고 싶을 경우, v-model:[변수명] 에 적절한 변수명을 넣어줌으로써 여러 변수에 각각 양방향 바인딩을 설정할 수 있다

v-slot

// TestTemplate.vue
<div class="wrapper">
    <slot name="first" />
    <span>and</span>
    <slot name="second" />
</div>
// TestView.vue
<TestTemplate>
    <template v-slot:first>
        <span>hihi</span>
    </template>
    <template v-slot:second>
        <span>byebye</span>
    </template>
</TestTemplate>

// 아래와 같다
<div class="wrapper">
    <span>hihi</span>
    <span>and</span>
    <span>byebye</span>
</div>

slot 예제를 참고하자

슬롯을 여러 개 배치하여 각각에 이름을 설정했을 때, 어떤 슬롯에 어떤 요소가 들어가야 하는지 v-slot 디렉티브를 이용하여 정의할 수 있다

v-slot 디렉티브는 커스텀 컴포넌트나 template 태그에만 적용할 수 있다

 

// TestView.vue
<TestTemplate>
    <template #first>
        <span>hihi</span>
    </template>
    <template #second>
        <span>byebye</span>
    </template>
</TestTemplate>

v-slot: 대신 #을 적어서 짧게 줄일 수 있다 (v-bind = :, v-on = @와 비슷한 이치)

v-pre

// template
<div v-pre>
    {{ value }}
</div>

// script
const value = '컴파일이 되어버렸당!!';

// 아래와 같다
<div>{{ value }}</div>

v-pre 가 설정된 태그의 모든 하위 요소들은, Vue 템플릿이 적용되지 않고 그대로 출력된다

보통 콧수염 괄호 ( {{ }} ) 를 그대로 출력하고 싶을 때 사용한다

v-once

// template
<div>
    {{ timer }}
</div>

// script
const timer = ref(0);

onMount(() => { // 마운트 시 한번만 실행되는 함수
    setInterval(() => {
        timer.value += 1;
    }, 1000);
});

위와 같은 예시를 만들어 보자

timer 변수는 1초에 한 번씩 증가한다

 

// template
<div v-once>
    {{ timer }}
</div>

// script
const timer = ref(0);

onMount(() => { // 마운트 시 한번만 실행되는 함수
    setInterval(() => {
        timer.value += 1;
    }, 1000);
});

하지만 이 경우, timer 변수가 변화하지 않는다 (출력된 값이 0인 채로 멈춰버린다)

v-once 디렉티브는 렌더링을 단 한 번만 수행하게 하며, 이후의 업데이트를 무시한다

후속 업데이트가 필요 없는 컴포넌트에 v-once 를 적용함으로써 성능 최적화를 수행할 수 있다

v-memo

<div v-memo="[value]">
    {{ value }}
</div>

요소와 하위 요소들을 메모이제이션 할 수 있다

위의 예시를 예로 들면, value 값 (디펜던시) 이 변하기 전까지 해당 요소의 업데이트는 무시된다 (가상 돔에서도 메모이징 된 복사본을 대신 사용한다)

v-once 가 디펜던시가 변경되었을 때만 임시 해제되는 느낌이라고 보면 된다

v-cloak

<div v-cloak>
    {{ value }}
</div>

요소가 컴파일되기 전까지 잠시 숨길 수 있다


참고 자료

https://vuejs.org/api/built-in-directives.html#v-model

'ClientSide > Vue' 카테고리의 다른 글

Vue 에서 Form input 관리하기  (0) 2023.10.26
Vue를 먹자 3 - 반응형 API  (0) 2023.10.24
Vue를 먹자 1  (0) 2023.10.20
Comments