Vue 인스턴스와 템플릿 문법

Vue 앱 제작 기초

Vue 인스턴스

모든 Vue 앱은 Vue 함수로 새 Vue 인스턴스를 만드는 것부터 시작하는데, 엄격한 기준으로는 MVVM 패턴과 관련이 없지만 Vue의 디자인은 부분적으로 MVVM 패턴에서 영감을 받았다고 합니다.

새 Vue 인스턴스 만들기
var vm = new Vue({
  ...
})

일반적으로 Vue 인스턴스를 참조하는 변수는 ViewModel의 약자인 vm을 사용하는데, Vue 인스턴스를 생성할 때 이 인스턴스에 options 객체를 전달해야 합니다.

Vue 앱은 new Vue를 통해 만들어진 루트 Vue 인스턴스로 구성되고, 선택적으로 중첩이 가능하고 재사용이 가능한 컴포넌트 트리로 구성되는데, 만약 Todo 앱을 만든다면 다음과 같이 컴포넌트 트리를 구성할 수 있습니다.

컴포넌트 트리 구성
Root Instance
└─ TodoList
   ├─ TodoItem
   │  ├─ DeleteTodoButton
   │  └─ EditTodoButton
   └─ TodoListFooter
      ├─ ClearTodosButton
      └─ TodoListStatistics

데이터와 메소드

Vue 인스턴스가 생성되면 data 객체에 있는 모든 속성이 Vue의 반응형 시스템에 추가되는데, 각 속성값이 변경되면 가 “반응”하여 새로운 값과 일치하도록 업데이트가 실행됩니다.

인스턴스 데이터의 상관관계
// 데이터 객체
var data = { a: 1 }

// Vue인스턴스에 데이터 객체를 추가
var vm = new Vue({
  data: data
})

// 인스턴스에 있는 속성은 원본 데이터에 있는 값을 반환
vm.a === data.a // => true

// 인스턴스에 있는 속성값을 변경하면 원본 데이터도 변경됨
vm.a = 2
data.a // => 2

// 반대도 마찬가지로 데이터가 변경됨
data.a = 3
vm.a // => 3

데이터가 변경되면 화면은 다시 렌더링되는데, data에 있는 속성들은 인스턴스가 생성될 때 존재한 것들만 반응한다는 것에 주의할 필요가 있습니다. 즉 다음과 같이 원본 데이터에는 없던 새로운 속성에는 반응하지 않습니다.

vm.b = 'hi'

즉, Vue 앱을 개발할 때 어떤 속성이 나중에 필요하다는 것을 알고 있고, 빈 값이거나 존재하지 않은 상태로 시작하는 경우에는 다음과 같이 data 객체에 초기값을 지정해줄 필요가 있습니다.

초기값 설정하기
data: {
  newTodoText: '',
  visitCount: 0,
  hideCompletedTodos: false,
  todos: [],
  error: null
}

반응성 제외

만약 데이터를 변경해도 자동으로 업데이트 되지 않도록 하고 싶다면, Object.freeze()를 사용하면 됩니다. Object.freeze()는 기존의 속성이 변경되는 것을 막아 반응성 시스템이 추적할 수 없도록 만들어 줍니다.

Object.freeze()
var obj = {
  foo: 'bar'
}
Object.freeze(obj)

new Vue({
  el: '#app',
  data: obj
})
<div id="app">
  <p>{{ foo }}</p>
  <!-- obj.foo는 더이상 변하지 않음 -->
  <button v-on:click="foo = 'baz'">Change it</button>
</div>
Vue 인스턴스 접근하기

Vue 인스턴스는 데이터 속성 이외에도 유용한 인스턴스 속성 및 메소드를 제공하는데, 다른 사용자 정의 속성과 구분하기 위해 $ 접두어를 붙여 속성과 메서드에 접근할 수 있습니다.

Vue 인스턴스에 접근하기
var data = { a: 1 }
var vm = new Vue({
  el: '#example',
  data: data
})

vm.$data === data                               // true
vm.$el === document.getElementById('example')   // true

// 인스턴스 메소드 $watch 사용
vm.$watch('a', function (newVal, oldVal) {
  // `vm.a`가 변경되면 호출
})

인스턴스 라이프사이클 훅

Vue 인스턴스는 생성될 때 일련의 초기화 단계를 거치게 되는데, 다음과 같은 각 초기화 단계에서 사용자 정의 로직을 실행할 수 있는 라이프사이클 훅도 호출될 수 있습니다.

Vue 인스턴스 초기화 단계
데이터 관찰 설정이 필요한 경우
템플릿을 컴파일하는 경우
인스턴스를 DOM에 마운트하는 경우
데이터가 변경되어 DOM를 업데이트하는 경우

다음의 코드는 인스턴스가 생성된 후에 호출되는 created로 사용자 정의 로직을 구현한 예제입니다.

인스턴스 생성 후 사용자 정의 로직 실행하기
new Vue({
  data: {
    a: 1
  },
  created: function() {
    // `this` 는 vm 인스턴스를 가리킴
    console.log('a is: ' + this.a) // "a is: 1"
  }
})

Vue는 created 외에도 여러 단계의 인스턴스 라이프사이클에서 호출될 수 있는 mounted, updated, destroyed와 같은 혹을 제공해주는데, 모든 라이프사이클에서 호출되는 훅에서 사용되는 thisVue 인스턴스를 가리키게 됩니다.

Vue에는 “컨트롤러”가 존재하지 않기 때문에, 컴포넌트의 사용자 지정 로직은 created, mounted, updated, destroyed의 라이프사이클 훅으로 분할하여 구현할 수 있습니다.

options 속성이나 콜백에는 다음과 같은 화살표 함수는 사용하지 않는 것이 좋습니다.

created: () => console.log(this.a) 이나 vm.$watch('a', newValue => this.myMethod())

화살표 함수는 this를 가지지 않기 때문에 화살표 함수에서 this는 다른 변수로 취급되거나 렉시컬하게 호출한 변수를 발견할 때까지 부모 스코프에서 해당 변수를 찾게 되는데, 이 때문에 Uncaught TypeError: Cannot read property of undefined 또는 Uncaught TypeError: this.myMethod is not a function와 같은 오류를 만날 수 있습니다.

Vue의 라이프사이클 다이어그램

템플릿 문법

Vue는 렌더링 된 DOM을 기본 Vue 인스턴스의 데이터에 선언적으로 바인딩 할 수있는 HTML 기반의 템플릿 구문을 사용하는데, 모든 Vue 템플릿은 스펙을 호환하는 브라우저 및 HTML 파서가 구문 분석을 할 수있는 유효한 HTML로 구성됩니다.

Vue는 내부적으로 템플릿을 가상 DOM 렌더링 함수로 컴파일하는데, 반응형 시스템과 결합된 Vue는 앱 상태가 변경 될 때 최소한으로 DOM을 조작하고, 다시 적용할 수 있는 최소한의 컴포넌트를 지능적으로 파악할 수 있습니다.

만약 가상 DOM 개념과 Javascript의 기본 기능에 익숙하다면 템플릿을 사용하는 대신, 렌더링 함수를 직접 작성하거나 React와 같이 JSX를 사용할 수도 있습니다.

데이터 바인딩

Vue는 템플릿 문법에서 문자열, 원시 HTML, 속성, Javascript 표현식과의 데이터 바인딩을 위해 몇 가지 형태의 보간법을 사용하고 있습니다.

문자열

데이터 바인딩의 가장 기본 형태는 이중 중괄호{{..}}를 사용한 텍스트 보간으로 이중 중괄호 태그는 해당 데이터 객체의 msg 속성 값으로 대체되고, 데이터 객체의 msg 속성이 변경될 때 마다 자동으로 갱신이 이루어집니다.

문자열 데이터 바인딩하기
<span>메시지: {{ msg }}</span>
v-once

다음과 같이 v-once 디렉티브를 사용하면, 데이터 변경 시에도 업데이트 되지 않는 일회성 보간을 수행할 수 있는데, v-once의 경우에는 같은 노드의 바인딩에도 영향을 미친다는 점에 유의할 필요가 있습니다.

데이터 바인딩 후 업데이트 제외하기
<span v-once>변경되지 않습니다: {{ msg }}</span>

원시 HTML

이중 중괄호는 HTML이 아닌 일반 텍스트로 데이터를 해석하기 때문에, 실제 HTML을 출력해야 하는 경우에는 v-html 디렉티브를 사용되는데, 다음의 코드를 실행하면 spanrawHtml로 대체되고 이 때 데이터 바인딩을 위한 v-html은 실행된 코드에서 무시되는 것을 확인할 수 있습니다.

실제 HTML 출력하기
<p>Using mustaches: {{ rawHtml }}</p>
<p>Using v-html directive: <span v-html="rawHtml"></span></p>

Vue는 문자열 기반 템플릿 엔진이 아니어서 v-html을 이용해 템플릿을 사용할 수 없기 때문에 UI 재사용 및 구성을 위해서는 컴포넌트를 사용하는 것을 고려하고, v-html의 경우 웹사이트에서 임의의 HTML을 동적으로 렌더링하는 것은 XSS 피싱 공격의 취약점으로 쉽게 이어질 수 있는 만큼, 신뢰할 수 있는 콘텐츠에서만 HTML 보간을 사용할 필요가 있습니다.

속성

HTML 속성에서는 이중 중괄호를 사용할 수 없는 대신, v-bind 디렉티브를 사용할 수 있습니다.

HTML 속성 데이터 바인딩하기
<div v-bind:id="dynamicId"></div>

v-bind에 일반 데이터를 바인딩하면 해당 데이터가 반영되지만, diabled와 같이 boolean 속성을 사용하는 경우 truedisabled="true"와 같이 작동하고, null, undefined, falsedisabled="false"와 같이 작동하는 것을 확인할 수 있습니다.

v-bind에 boolean 속성 사용하기
<button v-bind:disabled="isButtonDisabled">Button</button>

Javascript 표현식

Vue는 템플릿의 간단한 속성 키 뿐만 아니라, 모든 데이터 바인딩 내에서 Javascript 표현식의 모든 기능을 지원하는데, 다음과 같이 표현식은 Vue 인스턴스의 데이터 범위 내에서 Javascript로 계산이 됩니다.

올바른 표현식 사용
{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id"></div>

하지만 Vue는 각 바인딩에 하나의 단일 표현식만 포함될 수 있기 때문에, 다음과 같은 코드는 에러가 발생하게 됩니다.

잘못된 표현식 사용
{{ var a = 1 }} // 구문은 표현식이 아님

{{ if (ok) { return message } }} // 조건문은 삼항 연산자로 사용

템플릿 표현식은 샌드박스 처리되고, Math, Date와 같이 전역으로 사용 가능한 것에만 접근할 수 있기 때문에 템플릿 표현식에서 사용자 정의 전역에 액세스해서는 안됩니다.

디렉티브

디렉티브는 v- 접두사가 있는 특수 속성으로, v-for를 제외한 디렉티브의 속성 값은 단일 Javascript 표현식이 될 수 있습니다. 디렉티브의 역할은 표현식의 값이 변경될 때 반응적으로 DOM에 적용하는 것인데, 다음과 같이 v-if 디렉티브는 seen의 값에 따라 <p>엘리먼트를 제거하거나 삽입하는 동작을 할 수 있습니다.

v-if 디렉티브
<p v-if="seen">엘리먼트를 표시합니다.</p>

전달인자

일부 디렉티브는 콜론으로 표시되는 “전달인자”를 사용할 수 있는데, 다음과 같이 v-bind는 반응적으로 HTML 속성을 갱신하는데 사용되는 디렉티브입니다.

v-bind 디렉티브
<a v-bind:href="url"> ... </a>

위 코드에서 href는 전달인자라고 하는데, 엘리먼트의 href 속성을 표현식인 url의 값에 바인드하는 v-bind 디렉티브에게 알려주는 역할을 합니다.

v-on 디렉티브
<a v-on:click="doSomething"> ... </a>

위 코드의 경우에는 DOM 이벤트를 수신하는 v-on 디렉티브를 사용했는데, 전달인자는 이벤트를 받는 이름으로 사용자가 해당 엘리먼트를 클릭하면 실행할 함수를 바인드하는 이벤트 핸들링 디렉티브입니다.

동적 전달인자

Vue는 2.6.0버전부터 Javascript 표현식을 대괄호로 묶어 디렉티브의 argument로 사용하는 것이 가능해졌는데, 다음과 같이 attributeName은 Javascript형식으로 동적 변환되어, 그 변환결과가 전달인자의 최종적인 밸류로 사용됩니다.

예를들어 Vue 인스턴스에 href라는 값을 가진 attributeName 데이터 속성이 있다면, 이 바인딩은 v-bind:href와 동등한 역할을 하게 되는데, 동적 전달인자의 경우 사용 형식에 조금의 제약이 있기 때문에 사용에 주의할 필요가 있습니다.

동적 전달인자 사용하기
<a v-bind:[attributeName]="url"> ... </a>

attributeName과 유사하게, 동적인 이벤트명에 핸들러를 바인딩 할 때 동적 전달인자를 활용할 수 있는데, 다음과 같이 eventName의 값이 focus라면 v-on:[EventName]v-on:focus와 동등한 역할을 하게 됩니다.

동적 이벤트명 사용하기
<a v-on:[eventName]="doSomething"> ... </a>
동적 전달인자 값의 제약

동적 전달인자는 null을 제외하고는 string으로 변환될 것으로 예상하는데, 특수 값인 null은 명시적으로 바인딩을 제거하는데 사용되고, 그 외에 string이 아닌 값이 들어온다면 경고를 출력하게 됩니다.

동적 전달인자 형식의 제약

동적 전달인자의 형식에는 문자상의 제약이 있는데, 스페이스와 따옴표같은 몇몇 문자는 HTML의 속성명으로 적합하지 않은 문자이기 때문입니다.

잘못된 동적 전달인자 형식
<a v-bind:['foo' + bar]="value"> ... </a> // 컴파일러 경고 발생

위와 같은 컴파일러 경고를 피하려면 스페이스나 따옴표를 포함하지 않는 형식을 사용하거나, 복잡한 표현식을 계산된 속성으로 대체하면 되는데, 탬플릿이 HTML파일에 직접 쓰여진 경우에는 브라우저가 모든 속성명을 소문자로 만들기 때문에 대문자의 사용은 피하는것이 좋습니다.

in-DOM 방식의 제약
<a v-bind:[someAttr]="value"> ... </a>

위 코드와 같이 in-DOM 템플릿에서는 v-bind:[someAttr]v-bind:[someattr]로 변환되기 때문에, 인스턴스에 someattr속성이 없다면 코드가 동작하지 않을 수도 있습니다.

수식어

수식어는 점으로 표시되는 특수 접미사로 디렉티브를 특별한 방법으로 바인딩해야 한다는 것을 나타냅니다. 예를 들어, .prevent 수식어는 트리거된 이벤트에서 event.preventDefault()를 호출하도록 v-on 디렉티브에게 알려주는 역할을 하는데, 수식어는 v-onv-model을 사용할 때 주로 사용하게 됩니다.

prevent 수식어의 사용 예
<form v-on:submit.prevent="onSubmit"> ... </form>

약어

v- 접두사는 Vue에서 템플릿의 특정 속성을 식별하기 위한 시각적인 신호 역할을 하는데, Vue를 사용하여 기존의 마크업에 동적인 동작을 적용할 때 매우 유용합니다.

가장 자주 사용되는 디렉티브인 v-bind와 v-on은 약어로 사용할 수 있는데, 이들 디렉티브를 약어로 사용하는 경우에는 다음과 같이 각각 :@를 사용하면 됩니다.

v-bind의 약어 사용법
<!-- 전체 문법 -->
<a v-bind:href="url"> ... </a>

<!-- 약어 -->
<a :href="url"> ... </a>

<!-- shorthand with dynamic argument (2.6.0+) -->
<a :[key]="url"> ... </a>
v-on의 약어 사용법
<!-- 전체 문법 -->
<a v-on:click="doSomething"> ... </a>

<!-- 약어 -->
<a @click="doSomething"> ... </a>

<!-- shorthand with dynamic argument (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

답글 남기기