조건부 렌더링
v-if
v-if 디렉티브는 조건에 따라 블록을 렌더링하기 위해 사용되는데, 블록은 v-if
디렉티브의 표현식이 true
값을 반환할 때만 렌더링되고, v-else
와 함께 “else 블록”을 추가할 수도 있습니다.
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-if="awesome">Vue is awesome!</h1> <h1 v-else>Oh no ????</h1>
<template>에 v-if 조건부 그룹 만들기
v-if
는 디렉티브이기 때문에 하나의 엘리먼트에 추가해야 하는데, 하나 이상의 엘리먼트를 트랜지션해야 하는 경우에는 다음과 같이 보이지 않는 래퍼 역할을 하는 <template>
엘리먼트에 v-if
를 사용할 수 있습니다. <template>
엘리먼트는 최종 렌더링 결과에는 포함되지 않습니다.
<template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template>
v-else
v-else 디렉티브는 v-if에 대한 “else 블록”을 나타낼 수 있습니다. v-else
엘리먼트는 v-if
엘리먼트 또는 v-else-if
엘리먼트 바로 뒤에 있어야 되는데, 그렇지 않으면 Vue가 v-else
를 인식할 수 없기 때문에 주의해야 합니다.
<div v-if="Math.random() > 0.5"> 이제 나를 볼 수 있어요 </div> <div v-else> 이제는 안보입니다 </div>
v-else-if
v-else-if는 2.1.0+부터 새롭게 추가된 디렉티브로, v-if에 대한 “else if 블록” 역할을 하는 디렉티브입니다. v-else-if
는 여러 개를 사용할 수 있는데, v-else
와 마찬가지로 v-else-if
엘리먼트는 v-if
또는 v-else-if
엘리먼트의 바로 뒤에 와야 합니다.
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
key로 엘리먼트 렌더링 제어하기
Vue는 엘리먼트를 가능한 효율적으로 렌더링하기위해 엘리먼트를 처음부터 렌더링을 하지않고 다시 사용할 수 있는데, 엘리먼트를 재사용하는 것은 Vue를 매우 빠르게 만드는데 도움이 되고 이외에도 몇가지 유용한 이점이 있습니다.
사용자가 여러 로그인 유형을 트랜지션할 수 있도록 허용하는 경우에도 유용한데, 다음 코드와 같이 loginType
을 바꾸어도 input에 사용자가 이미 입력한 내용은 지워지지 않고, 두 템플릿이 모두 같은 요소를 사용하기 때문에 <input>
은 대체되지 않고 placeholder
만 변경되는 동작을 하게 됩니다.
<template v-if="loginType === 'username'"> <label>사용자 이름</label> <input placeholder="사용자 이름을 입력하세요"> </template> <template v-else> <label>이메일</label> <input placeholder="이메일 주소를 입력하세요"> </template>
그런데 위와 같이 엘리먼트를 재사용하는 것이 항상 바람직한 것은 아니기때문에, 두 엘리먼트를 완전히 별개의 엘리먼트로 인식시키기 위해 유일한 값을 key
속성으로 추가해서 사용할 수도 있습니다.
<template v-if="loginType === 'username'"> <label>사용자 이름</label> <input placeholder="사용자 이름을 입력하세요" key="username-input"> </template> <template v-else> <label>이메일</label> <input placeholder="이메일 주소를 입력하세요" key="email-input"> </template>
위의 코드에서는 input
에 각각 username-input
, email-input
이라는 key
를 추가했는데, 트랜지션이 이루어지는 경우 input
에 입력했던 값이 초기화되고 처음부터 렌더링되는 것을 확인할 수 있습니다.
참고로 <label>
엘리먼트는 key
속성이 없기 때문에 여전히 효율적으로 재사용될 수 있습니다.
v-show
v-show 디렉티브는 엘리먼트를 조건부로 표시하기 위한 또 다른 옵션입니다. 사용법은 v-if
와 거의 동일한데, v-show
가 있는 엘리먼트는 항상 렌더링 되고 DOM에 남아있다는 차이가 있습니다. 즉 v-show
는 단순히 엘리먼트에 CSS의 display
속성을 토글하는 디렉티브입니다.
<h1 v-show="ok">안녕하세요!</h1>
리스트 렌더링
v-for와 배열 매핑
v-for 디렉티브는 배열을 기반으로 리스트를 렌더링 할 수 있습니다. v-for
디렉티브는 리스트를 렌더링할 때 item in items
형태의 특별한 문법이 필요한데, items
는 원본 데이터 배열을 뜻하고, item
은 반복되는 배열 엘리먼트의 별칭을 뜻합니다.
<ul id="example-1"> <li v-for="item in items"> {{ item.message }} </li> </ul>
var example1 = new Vue({ el: '#example-1', data: { items: [ { message: 'Foo' }, { message: 'Bar' } ] } })
v-for
블록에는 부모 범위 속성에 대한 모든 권한을 가지고 있고, 현재 항목의 인덱스에 대한 두 번째 전달인자 옵션도 제공할 수 있습니다.
<ul id="example-2"> <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> </ul>
var example2 = new Vue({ el: '#example-2', data: { parentMessage: 'Parent', items: [ { message: 'Foo' }, { message: 'Bar' } ] } })
v-for
의 반복 문법에서는 in
대신, JavaScript의 이터레이터에 대한 자바스크립트 구문과 유사한 of
를 구분자로 사용할 수도 있습니다.
<div v-for="item of items"></div>
v-for와 객체
v-for는 객체의 속성을 반복하거나 키에 대한 두 번째 전달 인자를 제공할 수도 있습니다.
<ul id="v-for-object" class="demo"> <li v-for="value in object"> {{ value }} </li> </ul>
new Vue({ el: '#v-for-object', data: { object: { title: 'How to do lists in Vue', author: 'Jane Doe', publishedAt: '2016-04-10' } } })
v-for
에 객체를 사용하는 경우 다음과 같이 키에 대한 두 번째 전달 인자를 제공하거나 인덱스를 제공할 수 있는데, 객체를 반복하는 경우에는 Object.keys()
의 키 나열 순서에 따라 결정되기 때문에 배열과 같이 일정한 순서로 렌더링되지는 않습니다.
<div v-for="(value, name) in object"> {{ name }}: {{ value }} </div>
<div v-for="(value, name, index) in object"> {{ index }}. {{ name }}: {{ value }} </div>
Maintaining State
Vue가 v-for
에서 렌더링된 엘리먼트 목록을 갱신하는 경우에는 기본적으로 “in-place patch”라는 전략을 사용하는데, 이는 데이터 항목의 순서가 변경된 경우에 항목의 순서와 일치하도록 DOM 요소를 이동하는 대신 각 요소를 적절한 위치에 패치하고 해당 인덱스에서 렌더링할 내용을 반영하는지 확인합니다.
이 기본 모드는 효율적이지만 목록의 출력 결과가 하위 컴포넌트 상태이거나 폼 input과 같은 임시 DOM 상태에 의존하지 않는 경우에 적합합니다.
Vue에서 개별 DOM 노드들을 추적하고 기존 엘리먼트를 재사용, 재정렬하기 위해서는 v-for
의 각 항목들에 고유한 key
속성을 제공해야 되는데, key
에 대한 이상적인 값은 각 항목을 식별할 수 있는 고유한 ID가 될 수 있고, 이 특별한 속성은 v-bind
를 사용하여 동적 값에 바인딩을 해주어야 합니다.
<div v-for="item in items" v-bind:key="item.id"> <!-- content --> </div>
고유 ID를 바인딩하는 방식은 반복되는 DOM 내용이 단순한 경우나 의도적인 성능 향상을 위해 기본 동작에 의존하지 않는 경우를 제외하면, 언제나 v-for에 key를 추가하는 것이 좋습니다.
단 객체나 배열처럼, 기본 타입이 아닌 값을 키로 사용해서는 안되고, 문자열이나 숫자를 사용해야 하는데, key
는 Vue가 노드를 식별하는 일반적인 메커니즘이기 때문에 v-for
와 특별히 연관되지 않는 다른 용도로도 사용될 수 있습니다.
배열 변경 감지
변이 메소드
Vue는 감시중인 배열의 변이 메소드를 래핑하여 뷰 갱신을 트리거하는데, 다음과 같이 래핑된 메소드들은 콘솔을 열고 이전 예제의 items
와 같은 배열로 example1.items.push({ message: 'Baz' })
와 같이 변이 메소드를 호출하여 재생할 수 있습니다.
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
배열 대체
변이 메소드는 호출된 원본 배열을 변형하는데, 이와 비교하여 변형을 하지 않는 방법도 있습니다. 바로 filter()
, concat()
, slice()
메소드인데, 이 메소드들을 사용하면 원본 배열을 변형하지 않고 항상 새 배열을 반환하게 됩니다.
example1.items = example1.items.filter(function (item) { return item.message.match(/Foo/) })
이렇게 하면 Vue가 기존 DOM을 버리고 전체 목록을 다시 렌더링 한다고 생각할 수 있습니다. 하지만 다행히도, 그렇지는 않습니다. Vue는 DOM 요소 재사용을 극대화하기 위해 몇가지 똑똑한 구현을 하므로 배열을 겹치는 객체가 포함된 다른 배열로 대체하기 때문에 효율적입니다.
배열 변경 시 주의 사항
JavaScript의 제한으로 인해 Vue는 배열에 대해 다음과 같은 변경 사항을 감지할 수 없습니다.
vm.items[indexOfItem] = newValue
와 같이 인덱스로 배열에 있는 항목을 직접 설정하는 경우vm.items.length = newLength
와 같이 배열 길이를 수정하는 경우
var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // reactive하지 않음 vm.items.length = 2 // reactive하지 않음
위의 코드의 첫 번째와 같이 인덱스로 배열에 있는 항목을 직접 설정하는 경우에는 다음과 같은 코드로 반응 시스템을 트리거할 수 있습니다.
Vue.set(vm.items, indexOfItem, newValue)
vm.items.splice(indexOfItem, 1, newValue)
// vm.$set은 전역 Vue.set의 별칭 vm.$set(vm.items, indexOfItem, newValue)
두 번째와 같이 배열 길이를 수정하는 경우에는 다음과 같은 코드로 반응 시스템을 트리거할 수 있습니다.
vm.items.splice(newLength)
객체 변경 시 주의 사항
var vm = new Vue({ data: { a: 1 } }) // `vm.a` 는 반응형 vm.b = 2 // `vm.b` 는 반응형이 아님
모던 JavaScript의 한계로 Vue는 속성 추가 및 삭제를 감지하지 못하는데, Vue는 이미 만들어진 인스턴스에 새로운 루트레벨의 반응형 속성을 동적으로 추가하는 것을 허용하지 않습니다. 하지만 Vue.set(object, propertyName, value)
메소드를 사용하면 중첩된 객체에 반응형 속성을 추가할 수 있습니다.
var vm = new Vue({ data: { userProfile: { name: 'Anika' } } })
Vue는 위와 같은 객체에 다음의 방법으로 새로운 속성을 추가할 수 있습니다.
Vue.set(vm.userProfile, 'age', 27)
vm.$set(vm.userProfile, 'age', 27)
때로는 Object.assign()이나 _.extend()를 사용해 기존의 객체에 새 속성을 할당할 수 있는데, 이 경우에는 두 객체의 속성을 사용해 새 객체를 만들어 주어야 합니다.
Object.assign(vm.userProfile, { age: 27, favoriteColor: 'Vue Green' })
vm.userProfile = Object.assign({}, vm.userProfile, { age: 27, favoriteColor: 'Vue Green' })
필터링/정렬 결과 표시하기
원본 데이터를 실제로 변경하거나 재설정하지 않고 배열의 필터링 된 버전이나 정렬된 버전을 표시해야 하는 경우가 있는데, 이런 경우 필터링 된 배열이나 정렬된 배열을 반환하는 계산된 속성을 만들 수 있습니다.
<li v-for="n in evenNumbers">{{ n }}</li>
data: { numbers: [ 1, 2, 3, 4, 5 ] }, computed: { evenNumbers: function () { return this.numbers.filter(function (number) { return number % 2 === 0 }) } }
중첩 된 v-for
루프의 내부와 같이 계산된 속성을 실행할 수 없는 상황에서는 다음과 같이 methods
속성을 사용할 수 있습니다.
<li v-for="n in even(numbers)">{{ n }}</li>
data: { numbers: [ 1, 2, 3, 4, 5 ] }, methods: { even: function (numbers) { return numbers.filter(function (number) { return number % 2 === 0 }) } }
v-for Range
v-for는 배열 대신 숫자를 사용할 수 있는데, 이 경우 템플릿을 여러번 반복합니다.
<div> <span v-for="n in 10">{{ n }} </span> </div>
v-for 템플릿
v-for는 v-if 템플릿과 마찬가지로, <template>태그를 사용해 여러 엘리먼트의 블럭을 렌더링 할 수 있습니다.
<ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul>
v-for와 v-if
Vue는 v-if와 v-for를 동시에 사용하는 것을 추천하지 않습니다. 만약 동일한 노드에 두 가지의 디렉티브가 모두 있다면, v-for
가 v-if
보다 높은 우선순위를 갖게 되는데, 이 경우 v-if
는 루프가 반복될 때마다 실행이 되고, 이는 일부 항목만 렌더링 하려는 경우에 유용하게 사용될 수 있습니다.
<li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo }} </li>
위 코드의 경우 완료되지 않은 할일만 렌더링하게 되는데, 위의 방법 대신 실행을 조건부로 하는 것이 목적이라면 래퍼 엘리먼트나 <template>
을 사용하는 것이 좋습니다.
<ul v-if="todos.length"> <li v-for="todo in todos"> {{ todo }} </li> </ul> <p v-else>No todos left!</p>
v-for와 컴포넌트
v-for
는 사용자 정의 컴포넌트에 직접 사용할 수 있는데, 2.2.0+
버전에서 v-for
는 key
가 필수 속성이기 때문에 반드시 함께 사용해주어야 합니다.
<my-component v-for="item in items" :key="item.id"></my-component>
그런데 컴포넌트는 자체 범위가 분리되어있어 컴포넌트에 데이터를 자동으로 전달하지는 않습니다. 만약 반복할 데이터를 컴포넌트로 전달하고 싶다면 반드시 props를 함께 사용해 주어야 합니다.
<my-component v-for="(item, index) in items" v-bind:item="item" v-bind:index="index" v-bind:key="item.id" ></my-component>
컴포넌트에 item
을 자동으로 주입하지 않는 이유는 컴포넌트가 v-for
의 작동 방식과 밀접하게 결합되기 때문인데, 데이터의 출처를 명확히 하면 다른 상황에서도 컴포넌트를 재사용할 수 있기 때문입니다.
다음 할 일 목록 예제 코드에서 is="todo-item"
속성은 <li>
엘리먼트는 <ul>
안에서만 유효합니다. 즉 <todo-item>
과 같은 사용자 정의 컴포넌트와 동일한 작업을 한다고 볼 수 있지만, 이런 방식은 잠재적인 브라우저의 구문 분석 오류를 해결할 수 있습니다.
<div id="todo-list-example"> <form v-on:submit.prevent="addNewTodo"> <label for="new-todo">Add a todo</label> <input v-model="newTodoText" id="new-todo" placeholder="E.g. Feed the cat" > <button>Add</button> </form> <ul> <li is="todo-item" v-for="(todo, index) in todos" v-bind:key="todo.id" v-bind:title="todo.title" v-on:remove="todos.splice(index, 1)" ></li> </ul> </div>
Vue.component('todo-item', { template: '\ <li>\ {{ title }}\ <button v-on:click="$emit(\'remove\')">Remove</button>\ </li>\ ', props: ['title'] }) new Vue({ el: '#todo-list-example', data: { newTodoText: '', todos: [ { id: 1, title: 'Do the dishes', }, { id: 2, title: 'Take out the trash', }, { id: 3, title: 'Mow the lawn' } ], nextTodoId: 4 }, methods: { addNewTodo: function () { this.todos.push({ id: this.nextTodoId++, title: this.newTodoText }) this.newTodoText = '' } } })