티스토리 뷰

Vue.JS

[Vue.JS] 리스트 렌더링

버미노트 2018. 11. 25. 21:42

이번 포스트에서는 v-for을 사용하여 리스트 렌더링 하는 방법에 대해 이야기 할 것입니다.

1. v-for와 배열

v-for 디렉티브를 사용하여 배열을 리스트 렌더링 할 수 있습니다. v-for 디렉티브에 item in items 형태의 문법을 넘겨줘야 합니다. items는 원본 데이터 배열이고 item은 반복되는 배열의 요소입니다.

기본 사용방법

<div id="app">
  <ul>
    <li v-for="item in items">
      {{ item.message }}
    </li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
});

위의 예제는 배열을 사용한 기본적인 리스트 렌더링 예제입니다. 또한 배열의 몇번째 인덱스인지 알 수 있는 인덱스 정보도 제공합니다.

<div id="app">
  <ul>
    <li v-for="(item, index) in items">
      {{ index }} - {{ item.message }}
    </li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
});

위의 예제와 같이 배열의 몇번째 요소를 렌더링 하고 있는지 확인 하기 위해서는 (item, index) in items 문법을 사용하면 됩니다. 또한 자바스크립트의 for .... of ... 문법과 동일하게 in 대신 of를 사용할 수도 있습니다.

<div id="app">
  <ul>
    <li v-for="item of items">
      {{ item.message }}
    </li>
  </ul>  
</div>
new Vue({
  el: '#app',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
});

자바스트립트와 동일하게 이터레이터(반복가능한..)에만 of를 사용 할 수 있습니다.

v-for에 배열을 사용한 예제는 CodePen에서 결과를 확인 할 수 있습니다.

2. v-for와 객체

v-for를 사용하여 객체의 속성을 반복 할 수도 있습니다.

<div id="app">
  <ul>
    <li v-for="value in object">
      {{ value }}
    </li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    object: {
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    }
  }
});

배열을 사용하여 리스트 렌더링할 때와 유사하게, key에 대한 두번째 전달인자를 제공합니다.

<div id="app">
  <ul>
    <li v-for="(value, key) in object">
      {{ key }} : {{ value }}
    </li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    object: {
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    }
  }
});

또한 인텍스도 제공합니다.

<div id="app">
  <ul>
    <li v-for="(value, key, index) in object">
      {{ index }}. {{ key }} : {{ value }}
    </li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    object: {
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    }
  }
});

객체를 반복하는 순서는 Object.keys()의 키 나열 순서에 따라 결정됩니다. 이 순서는 JavaScript 엔진에 따라 다를 수 있습니다.

v-for에 객체를 사용한 예제는 CodePen에서 결과를 확인 할 수 있습니다.

3. key

key는 Vue의 각각의 노드에 고유한 ID를 지정해 줄 때 사용됩니다. v-for 뿐만 아니라 이전 포스트에서 이야기한 v-if를 사용할 때도 key가 사용됩니다.

<!-- 변경 전 -->
<ul>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>
<!-- 변경 후 -->
<ul>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>

위의 예제는 <li>1</li> 가 제거된 간단한 예제입니다. Vue는 위의 예제의 동작을

  • <li>1</li>에서 <li>2</li>로 수정
  • <li>2</li>에서 <li>3</li>로 수정
  • <li>3</li>에서 <li>4</li>로 수정
  • <li>5</li> 제거

로 총 4번 업데이트 합니다. 이렇게 되면 가상 DOM을 쓰는 것이 무색할 정도로 효율이 좋아 보이지 않습니다. 이 때 key를 사용하여 최적화 할 수 있습니다.

<!-- 변경 전 -->
<ul>
  <li key="1">1</li>
  <li key="2">2</li>
  <li key="3">3</li>
  <li key="4">4</li>
</ul>
<!-- 변경 후 -->
<ul>
  <li key="2">2</li>
  <li key="3">3</li>
  <li key="4">4</li>
</ul>

위의 예제는 key를 사용하여 각 노드에 고유한 ID를 지정해 준 예제입니다. key를 지정해 주게 되면 가상 DOM은 key를 추적하여 key가 서로 매칭되는 노드를 업데이트 하게 됩니다. 위의 예제의 동작은

  • <li key="1">1</li> 제거

로 1번만 업데이트 하면 됩니다. 그렇기 때문에 v-for을 사용할 때 항상 key를 지정해 주는 것이 좋습니다. key 예제는 CodePen에서 확인 할 수 있습니다.

* 참고 - key는 실제로 렌더링 되지 않습니다.

key 속성은 Vue의 각각의 노드를 구별하기 위한 ID일 뿐 실제로 DOM에 렌더링 되지 않습니다. 또한 key 속성은 전역으로 고유하지 않아도 됩니다. 형제 노드끼리만 고유한 값이면 됩니다.

4. 배열 변경 감지

Vue가 배열이 변경 되었는지 감지하여 DOM을 업데이트 하게 하기 위해서는 배열의 메소드들을 사용해야 합니다.

변이 메소드

Vue가 배열의 변경을 감지 할 수 있는 배열의 메소드들은 아래와 같습니다.

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

위의 배열의 메소드를 사용할 때 Vue는 배열의 변화를 감지하여 DOM을 업데이트 할 수 있습니다.

배열 대체

변이 메소드에서 이야기 한 메소드들은 원본 배열 자체를 변형합니다.

  • filter()
  • concat()
  • slice()

반면 위의 메소드들은 원본 배열을 변형하지 않고 항상 새로운 배열을 반환합니다. 배열이 변형되지 않는 위의 메소드들을 사용할 때는 이전의 배열을 새로운 배열로 바꿔줘야 합니다.

<div id="app">
  <ul>
    <li v-for="value in array">{{ value }}</li>
  </ul>
  <button @click="filter">Filter</button>
</div>
new Vue({
  el: '#app',
  data: {
    array: [1, 2, 3, 4]
  },
  methods: {
    filter() {
      this.array = this.array.filter(x => x > 1);
    }
  }
});

위의 코드처럼 작성할 경우 전체 목록을 다시 렌더링 한다고 생각할 수 있지만 Vue는 가상 DOM을 사용하여 변화된 부분만 DOM에 반영하기 때문에 전체 목록을 다시 렌더링 하지 않고, 효율적으로 DOM을 업데이트 할 수 있습니다.

주의 사항

배열의 변화를 감지하지 못하는 몇몇의 방법이 있습니다. 이 방법을 사용할 때 주의가 필요합니다.

1) 인덱스로 배열에 있는 항목을 직접 설정하는 경우

this.items[indexOfItem] = newValue

인덱스로 배열에 있는 항목을 직점 설정한 경우 배열 변경을 감지할 수 없습니다.

위의 코드는 Vue가 배열의 변경을 감지 할 수 없습니다.

this.items.splice(indexOfItem, 1, newValue)

splice를 사용하여 배열 변경을 감지 할 수 있게 합니다.

배열의 변경을 감지하기 위해서는 위와 같이 splice로 구현해야 합니다.

2) 배열 길이를 수정하는 경우

this.items.length = newLength

배열 길이를 수정하는 경우 배열 변경을 감지 할 수 없습니다.

위의 코드와 같이 직접 배열 길이를 수정하는 경우 Vue는 배열의 변경을 감지 할 수 없습니다.

this.items.splice(newLength)

splice를 사용하여 배열 변경을 감지 할 수 있게 합니다.

배열의 변경을 감지하기 위해서는 위와 같이 splice로 구현해야 합니다.

5. 객체 변경 감지에 관한 주의사항

Vue는 속성 추가 및 삭제를 감지하지 못합니다.

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` 는 반응형입니다.

vm.b = 2
// `vm.b` 는 반응형이 아닙니다.

동적으로 추가된 속성은 반응형이 아닙니다.

Vue는 새로운 반응형 속성을 독적으로 추가하는 것이 불가능합니다. 동적으로 반응형 속성을 추가하는 몇가지 방법이 있습니다.

Vue.set(object, key, vale)

Vue.set(object, key, value)를 사용하여 동적으로 반응형 속성을 추가할 수 있습니다.

var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})

위와 같이 userProfile 객체에 새로운 반응형 속성 age를 추가하기 위해서는

Vue.set(vm.userProfile, 'age', 27)

위와 같이 Vue.set을 사용하여 새로운 반응형 속서을 추가 할 수 있습니다. Vue 인스턴스 메소드인 $set을 사용할 수도 있습니다. (Vue.set은 전역 메소드 입니다.)

vm.$set(this.userProfile, 'age', 27)

Object.assign()

Object.assign()이나 _.extend()를 사용해 기존 객체에 새 속성을 추가 할 수도 있습니다.

this.userProfile = Object.assign({}, this.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

Object.assign()을 사용하면 새로운 객체를 만들어 다시 할당해 주어야 합니다.

6. 필터링 / 정렬 된 결과 표시하기

원본 데이터를 실제로 변경하지 않고 필터링 되거나 정렬 된 배열을 표시해야 할 필요가 있습니다. 이 경우 필터링 된 배열이나 정렬된 배열을 반환하는 computed 속성을 만들 수 있습니다.

<div id="app">
  <ul>
    <li v-for="n in evenNumbers">{{ n }}</li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    numbers: [ 1, 2, 3, 4, 5 ]
  },
  computed: {
    evenNumbers() {
      return this.numbers.filter(function (number) {
        return number % 2 === 0
      })
    }
  }
});

computed 속성을 사용할 수 없는 경우 mehods 속성을 사용할 수 있습니다.

<div id="app">
  <ul>
    <li v-for="n in evenNumbers">
      <div v-for="number in multiplex(n)">{{ number }}</div>
    </li>
  </ul>  
</div>
new Vue({
  el: '#app',
  data: {
    numbers: [ 1, 2, 3, 4, 5 ]
  },
  computed: {
    evenNumbers() {
      return this.numbers.filter(function (number) {
        return number % 2 === 0
      })
    }
  },
  methods: {
    multiplex(number) {
      return number * 2
    }
  }
});

위의 예제는 CodePen에서 결과를 확인 할 수 있습니다.

7. Range v-for

v-for는 숫자를 사용할 수 있습니다.

<div id="app">
  <span v-for="n in 10">{{ n }} </span>
</div>

v-for는 숫자를 사용할 수 있습니다.

위의 예제는 CodePen에서 결과를 확인 할 수 있습니다.

8. v-for 템플릿

<template> 태그와 함께 v-for을 사용할 수 있습니다.

<div id="app">
  <ul>
    <template v-for="number in numbers">
      <li>{{ number }}</li>
      <li>{{ number * number }}</li>
    </template>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    numbers: [1, 2, 3, 4]
  }
});

<template>v-for는 함께 사용 할 수 있습니다.

위 코드의 예제는 CodePen에서 확인 할 수 있습니다.

9. v-forv-if

v-forv-if도 역시 함께 사용할 수 있습니다. 동일한 노드에 v-forv-if가 모두 존재한다면, v-for가 더 높은 우선순위를 가집니다. 즉, 루프가 반복될 때 마다 v-if가 실행됩니다.

<div id="app">
  <ul>
      <li v-for="number in numbers" v-if="number % 2 === 0">{{ number }}</li>
  </ul>
</div>
new Vue({
  el: '#app',
  data: {
    numbers: [1, 2, 3, 4]
  }
});

v-forv-if 보다 우선순위가 높습니다.

v-for가 동작을 할지 동작을 하지 말아야 하는지를 결정해야 한다면, 상위 엘리먼트(또는 <template>)에서 v-if를 사용해야 합니다.

<div id="app">
  <ul v-if="numbers.length">
    <li v-for="number in numbers">{{ number }}</li>
  </ul>
  <ul>
    <template v-if="numbers.length">
      <li v-for="number in numbers">{{ number }}</li>
    </template>
  </ul>  
</div>
new Vue({
  el: '#app',
  data: {
    numbers: [1, 2, 3, 4]
  }
});

위의 코드는 CodePen에서 결과를 확인할 수 있습니다.

10. v-for와 컴포넌트

v-for은 사용자 정의 컴포넌트에서도 사용할 수 있습니다.

<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>

사용자 정의 컴포넌트에서 데이터를 사용하기 위해 props로 데이터를 넘겨준 예제를 살펴도록 하겠습니다.

<div id="todo-list-example">
  <input
    v-model="newTodoText"
    @keyup.enter="addNewTodo"
    placeholder="Add a todo"
  >
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      :key="todo.id"
      :title="todo.title"
      @remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>
const TodoItem = {
  template: `
    <li>
      {{ title }}
      <button @click="$emit('remove')">X</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 = ''
    }
  },
  components: {
    TodoItem
  }
}) 

사용자 정의 컴포넌트에 v-for 사용하기

  • is="todo-item" : <ul> 엘리먼트 안에는 <li> 엘리먼트만 사용 가능합니다. 위의 예제에서 is="todo-item"을 사용하지 않고, <todo-item>을 사용할 수 있지만 브라우저의 구문 오류를 해결하기 위해 is="todo-item"을 사용하여 브라우저 구문 오류를 해결 하였습니다.
  • :title이라는 props를 사용하여 사용자 정의 컴포넌트에 데이터를 넘겨줍니다.
  • @romve라는 props를 사용하여 사용자 정의 컴포넌트에 이벤트 리스너를 넘겨줍니다. 사용자 정의 컴포넌트에서 $emit('remove') 호출시 @remove에 넘겨진 함수가 실행됩니다.

위의 예제는 CodePen에서 결과를 확인 할 수 있습니다.

'Vue.JS' 카테고리의 다른 글

[Vue.JS] 폼 입력 바인딩  (0) 2018.12.15
[Vue.JS] 이벤트 핸들링  (0) 2018.12.06
[Vue.JS] 조건부 렌더링  (0) 2018.11.20
[Vue.JS] 클래스와 스타일 바인딩  (0) 2018.11.13
[Vue.JS] computed와 watch  (0) 2018.10.31
댓글
공지사항
최근에 올라온 글