본문 바로가기
개발적인

Vue 공식 문서 훑어보기 - 템플릿 문법 · 반응형 기초 · 계산되어진 속성

by klm hyeon woo 2024. 3. 19.

목차

· 템플릿 문법

· 반응형 기초

· 계산되어진 속성


템플릿 문법

데이터 바인딩의 가장 기본적인 형태는 `이중 중괄호` 문법을 사용하는 방법입니다.

<span> 메시지 : {{ msg }} </span>

이중 중괄호 태그 내 msg 는 해당 컴포넌트 인스턴스의 msg 속성의 값으로 대체된다. 또한 msg 속성이 변경될 때마다 업데이트가 되어집니다. 이중 중괄호는 데이터를 HTML이 아닌 일반 텍스트로 해석합니다. 실제 HTML을 출력하려면 v-html 디렉티브를 사용해야합니다.

v-html 이라는 새로운 속성은 디렉티브라고 하며, 디렉티브는 Vue에서 제공하는 특수한 속성임을 나타내기 위해 접두사 v-를 사용하며, 렌더링된 DOM에 특별한 반응적 동작을 적용합니다.

<p> 텍스트 보간법 사용 : {{ rawHtml }} </p>
<p>v-html 디렉티브 사용: <span v-html="rawHtml"></span></p>

위 코드에 따른 결과 값

속성 바인딩

이중 중괄호는 HTML 속성(attribute) 내에서 사용할 수 없습니다.

<div v-bind:id="dynamicId"></div>

v-bind 디렉티브는 엘리먼트의 id 속성을 컴포넌트의 dynamicId 속성과 동기화된 상태(바인딩 속성을 지정했다면, 값이 필연적으로 있어야 한다)로 유지하도록 Vue에 지시합니다. 바인딩된 값이 null 또는 undefined이면 엘리먼트의 속성이 제거된 상태로 렌더링됩니다.

 

1. 단축 문법

v-bind는 매우 일반적으로 사용되기 때문에, 전역 단축 문법이 있습니다.

<div :id="dynamicId"></div>

:로 시작하는 속성은 일반 HTML과 약간 다르게 보일 수 있지만, 실제로 유효한 속성명 문자열이며 Vue를 지원하는 모든 브라우저에서 올바르게 파싱할 수 있다. 또한 최종 렌더링된 마크업에는 표시되지 않는다. 단축 문법은 선택 사항이지만, 사용할 경우 유용하다.

 

2. 동일 이름 축약 (Vue 3.4+)

속성의 이름이 바인딩되는 자바스크립트 값과 같을 경우 속성 값을 생략을 통해 문법을 더욱 간소화할 수 있습니다.

<!-- :id="id"와 동일 -->
<div :id></div>

<!-- 이것도 작동합니다 -->
<div v-bind:id></div>

불리언(Boolean) 속성

불리언 속성은 엘리먼트에 표기했는지 여부로 참/거짓 값을 나타내는 속성이다. disabled는 가장 일반적으로 사용되는 불리언 속성 중 하나입니다.

<button :disabled="isButtonDisabled">버튼</button>

isButtonDisabled에 truthy 값이 있을 경우, disabled 속성이 표기됩니다.

값이 빈 문자열인 경우 <button disabled="">의 일관성을 유지하므로 속성이 표기되며, 그 외 falsy 값의 경우 속성이 생략됩니다.

여러 속성을 동적으로 바인딩

여러 속성을 나타내는 JavaScript 객체가 있는 경우에도 인자 없이 v-bind를 사용하여 단일 엘리먼트에 바인딩을 할 수 있습니다.

const obj = {id: "container", class: "wrapper"}의 객체를 <div v-bind="obj"></div>

바인딩을 하면 id 값과 class 값이 들어가는 것을 확인할 수 있다.

자바스크립트 표현식 사용

Vue 템플릿에서 자바스크립트 표현식은 이중 중괄호 내부 모든 Vue 디렉티브 속성 (v-로 시작하는 특수 속성) 내부의 위치에서 사용을 할 수 있습니다. 각 바인딩에는 하나의 단일 표현식만 포함이 될 수 있으며, return 뒤에 사용할 수 있는 코드여야하며, (선언식을 작성하거나, if문과 같은 흐름 제어문들은 작동하지 않는다) 바인딩 표현식 내부에 컴포넌트에서 노출하는 메서드를 호출할 수 있습니다.

<title :title="toTitleDate(date)" :datetime="date">
    {{ formatDate(date) }}
</time>

* 바인딩 표현식 내부에서 호출되는 함수는 컴포넌트가 업데이트될 때마다 호출되므로, 데이터를 변경 또는 비동기 작업을 트리거하는 등의 사이드 이펙트가 없어야합니다.

템플릿 표현식은 샌드박스 처리되어 제한된 전역 리스트에만 접근을 할 수 있다. Math 및 Date 등 일반적으로 사용되는 기본 제공 전역 객체를 표시합니다. 단, 리스트에 명시적으로 포함되지 않는 window와 같은 전역 속성은 템플릿 표현식에서 접근할 수 없지만 app.config.globalProperties에 추가하여 Vue 내부의 모든 표현식에서 전역 속성에 접근할 수 있도록 명시적으로 정의할 수 있습니다.

디렉티브

v- 접두사가 있는 특수한 속성을 디렉티브라고 합니다. Vue는 v-html과 v-bind를 포함한 빌트인 디렉티브를 제공합니다.

빌트인 디렉티브란?
Vue에서는 몇가지 디렉티브를 기본적으로 제공하고 있는데, 이들을 빌트인 디렉티브라고 부른다.

디렉티브 속성 값은 자바스크립트 표현식(v-for, v-on v-slot을 제외한)이여야하며, 디렉티브의 역할은 표현식 값이 변경될 때마다 DOM에 반응적으로 업데이트를 적용하는 것입니다. 

 

일부 디렉티브는 디렉티브 뒤에 콜론(:)으로 표시되는 "인자"를 사용할 수 있습니다. 예를 들어 v-bind 디렉티브는 HTML 속성을 반응적으로 업데이트하는데 사용됩니다.

<a v-bind:href="url"> ... </a>
<a :href="url"> ... </a>

위와 같이 href는 v-bind 디렉티브의 인자로서, 엘리먼트 속성인 href에 url의 값을 바인딩한 것입니다.

간단하게 인자 앞의 v-bind:는 :로 줄여쓸 수 있습니다.

 

또 다른 예로는 DOM 이벤트를 수신하는 v-on 디렉티브이다.

<a v-on:click="doSomething"> ... </a>
<a @click="doSomething"> ... </a>

여기서 click의 인자는 수신할 이벤트 이름이다. v-on은 단축형으로 @를 대신 사용할 수 있습니다.

디렉티브의 인자를 대괄호로 감싸서 JavaScript 표현식으로 사용할 수 있습니다.

<a v-bind:[attributeName]="url"> ... </a>
<a :[attributeName]="url"> ... </a>

여기서 attributeName은 자바스크립트 표현식으로 동적으로 평가되며, 평가된 값은 인자의 최종 값으로 사용이 됩니다.

예를 들어 컴포넌트 인스턴스의 데이터에 attributeName 속성 값이 href인 경우 바인딩은 v-bind:href와 같습니다. 이와 유사하게 핸들러에도 이벤트 이름을 동적으로 바인딩 할 수 있습니다.

<a v-on:[eventName]="doSomething"> ... </a>
<a @[eventName]="doSomething"> ... </a>
· 동적인 인자 값 제약 조건
- 동적인 인자는 null 또는 문자열로 평가되어야 한다.
- 값이 null 일 경우, 바인딩을 명시적으로 제거한다.
- 문자열이 아닌 다른 값은 에러를 트리거한다.

· 동적인 인자 문법 제약 조건
- 동적인 인자 표현식에는 공백 및 따옴표와 같은 특정문자가 HTML 속성 이름 내에서 유효하지 않기 때문에 문법에 일부 제약 조건이 있다.
- DOM 내 템플릿(HTML 파일에 직접 작성된 템플릿)을 사용할 때, 브라우저가 속성 이름을 소문자로 강제 변환하므로 대문자로 키 이름을 지정하는 것을 피해야한다.
(만약, 동적 인자의 명칭을 someAttr이라고 했다면, DOM 내 템플릿에서 someattr로 변환이 되며 컴포넌트에 someattr 대신 someAttr 속성을 사용했다면 코드가 작동하지 않는다)

수식어

수식어는 점(.)으로 시작하는 특수한 접미사로, 디렉티브가 특별한 방식으로 바인딩되어야 함을 나타낸다. 

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

와 같이 .prevent 수식어는 트리거된 이벤트에서 event.preventDefault()를 호출하도록 v-on 디렉티브에 지시합니다.

 


반응형 기초 

Ref

Composition API에서 반응형 상태를 선언하는 권장 방법은 ref() 함수를 사용하는 것입니다.

import { ref } from "vue"
const count = ref(0);
/* ref()는 인수를 가져와서 .value 속성이 있는 ref 객체에 래핑하여 반환한다 */
const count = ref(0);
console.log(count) // { value : 0 }
console.log(count.value) // 0
count.value ++;
console.log(count.value) // 1
 
/* 컴포넌트 템플릿의 ref에 엑세스하려면, 컴포넌트의 setup() 함수에서 선언하고 반환한다 */
 
import { ref } from ‘vue’
export default {
setup() {
    const count = ref(0);
      return {
         count;
      }
   }
}

템플릿에서 ref를 사용할 때는 .value를 추가할 필요가 없다. 편의상 ref는 템플릿 내에서 사용될 때 자동으로 언래핑됩니다. 템플릿에서 ref를 사용하고 나중에 ref의 값을 변경하면, vue는 자동으로 이 변경을 감지하고 DOM을 적절하게 업데이트합니다. 컴포넌트가 처음 렌더링 될 때, Vue는 렌더링 과정에서 사용된 모든 ref를 추적합니다. 나중에 ref가 변경되면 내부적으로 Vue는 getter에서 추적을 수행하고, setter에서 트리거를 수행하며 객체 속성의 get 및 set 연산을 가로챌 수 있습니다.

<script setup>

setup()을 통해 상태와 메소드를 수동으로 노출하는 것은 장황할 수 있다. <script setup>으로 사용법을 단순화할 수 있습니다. <script setup>에서 선언된 최상위 수준 가져오기, 변수 및 함수는 동일한 컴포넌트의 템플릿에서 자동으로 사용할 수 있습니다. 자연스럽게 함께 선언된 모든 항목에 액세스할 수 있습니다.

깊은 반응형

Refs는 깊게 중첩된 개체, 배열 또는 Map과 같은 자바스크립트 내장 데이터 구조를 포함하여 모든 값 유형을 보유할 수 있습니다.

ref는 중첩된 객체나 배열을 변경하더라도 변경 사항이 감지됩니다.

state.value.count = 2 // 깊은 반응형이라면 트리거, 얕은 반응형이라면 트리거를 하지 않음
state.value = { count : 2} // 깊은 반응형, 얉은 반응형 모두 트리거

DOM 업데이트 타이밍

반응 상태를 변경하면 DOM이 자동으로 업데이트 된다. 하지만 DOM 업데이트는 동기적으로 적용되지 않는 다는 점을 생각하면 Vue는 업데이트 주기의 "다음 틱"까지 버퍼링하여 얼마나 많은 상태 변경을 수행하든 각 컴포넌트가 한 번만 업데이트 되도록 합니다. 상태 변경 후, DOM 업데이트가 완료될 때까지 기다리려면 nextTick() 전역 API를 사용할 수 있습니다.

import { nextThick } from 'vue';
 
async function increment() {
     count.value++;
 
     await nextTick();
 
     // 이제 DOM이 업데이트 되었습니다.
}
· OnMounted와 nextTick이 어떤 차이가 있을까?
mounted는 사용자가 사용하는 브라우저의 DOM에 모든 변경사항을 반영하고나서 실행되는 훅입니다. 그런데 자바스크립트 비동기 처리 특성때문에 DOM을 탐색하거나 수정하는 로직이 있을 경우, DOM이 갱신이 되기 전에 DOM을 탐색해서 undefined나 null에러가 발생하는 경우가 있습니다. 이런 상황 때문에 Vue 프레임워크에서는 DOM에 모든 변경 사항이 완전히 반영된 후에 사용자 정의 로직을 실행할 수 있도록 하는 함수입니다.

Reactive

반응형을 선언하는 또 다른 방법은 reactive() API를 사용하는 것입니다. 내부 값을 특수 객체로 감싸는 ref와 달리 reactive()는 객체 자체를 반응형으로 만들며 reactive()의 반환 값은 원본 객체와 같지 않고, 원본 객체를 재정의한 프록시입니다. 프록시는 반응형으로 아무리 원본 객체를 변경해도 화면 업데이트가 트리거 되지 않습니다. 따라서 객체를 Vue의 반응형 시스템으로 작업할 때 가장 좋은 방법은 상태를 재정의한 프록시만을 사용하는 것이 좋습니다.

* 프록시의 일관된 접근 보장을 위해 원본 객체를 reactive() 한 프록시와 프록시를 reactive()한 프록시는 동일한 프록시를 반환하도록 동작합니다. (raw 데이터의 프록시를 단순 비교)

* 프록시는 원본 데이터인 타겟과, 프록시의 동작을 규정하는 핸들러로 구성이 되는데, 프록시를 통해 생성한 객체의 속성이 변경되는 경우 handler 내의 set 함수를 통해서 상태가 변하게 됩니다.

이 규칙은 중첩된 객체에도 적용되어 내부 깊숙이까지 반응형이므로, 반응형 객체 내부의 중첩된 객체도 프록시입니다. reactive에는 몇가지 제한 사항이 있습니다.

  1. 제한된 값 유형, reactive는 객체 유형에서만 작동을 한다.
  2. 기본 유형을 사용하고자 한다면, ref를 사용해야한다.
  3. reactive()를 통한 프록시 참조가 항상 유지해야하며, reactive()를 통한 재선언을 하면 반응성 연결이 끊어지며 반응성 개체를 쉽게 대체할 수 없다.
  4. 반응형 객체의 원시 타입 속성을 지역 변수로 분해하거나, 그 속성을 함수에 전달할 때 반응성 연결이 끊어진다. 끊어지지 않고 반응성을 유지하고자 한다면 전체 개체를 전달해야한다.
let state = reactive({ count: 0 })
 
/**
* 위의 ({ count: 0})은 더 이상 추적되지 않는다.
* 반응성 연결이 끊어졌다.
*/
state = reactive({ count: 1})

 


계산되어진 속성

템플릿에 일부 표현식만 있다면 상관이 없겠지만, 너무 많은 논리에 대한 부분들을 다루게 되면 그에 따른 양이 방대해져 유지관리가 어려워질 수 있습니다.

const author = reactive({
    name: "john",
    books: [
      "first test",
      "twice test"
    ]
});

<p> 책을 가지고 있다. </p>
<span> {{ author.books.length > 0 ? "yes" : "no" }} </span>

템플릿의 반응형 결과가 author.books에 의해 계산된다는 점보다, 템플릿 내에서 이 코드를 두 번 이상 반복하고 싶지 않을 경우

반응형 데이터를 포함하는 복잡한 논리를 여러 번 반복하는 것보다 계산된 속성을 사용하여 성능을 개선하는 것이 좋습니다.

<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
    name: "john",
    books: [
      "first test",
      "twice test"
    ]
});

const publishedBooksMessage = computed(() => {
   return author.books.length > 0 ? "yes" : "no"
});
</script>

<template>
   <p> 책을 가지고 있다 </p>
   <span> {{ publishedBooksMessage }} </span>
</template>

computed() 함수에는 getter로 사용될 함수가 전달되어야하며, 반환되는 값은 computed ref 입니다.

일반 ref와 유사하게 계산된 결과를 publishedBooksMessage.value로 접근할 수 있습니다.

계산된 ref는 일반 ref와 역시 유사하게 템플릿에서는 자동으로 언래핑되어집니다.

템플릿 표현식에서는 .value 없이 참조할 수 있습니다.

댓글