For AI agents: the complete documentation index is available at https://nhanbe.id.vn/llms.txt, the full documentation bundle is available at https://nhanbe.id.vn/llms-full.txt, and this page is available as Markdown at https://nhanbe.id.vn/frontend/vue2.7/list-rendering.md.

Rendering lists with v-for directive, key binding, and array mutation detection in Vue 2.7.

List Rendering

Lặp render danh sách bằng v-for. Trong AEM, list thường là mảng JSON từ Sling Model (vd danh sách card, link, item của multifield) được parse ra rồi render.

v-for với mảng

Cú pháp item in items. Có thể lấy thêm index bằng (item, index) in items.

<ul>
  <li v-for="(user, index) in users" :key="user.id">
    {{ index }} - {{ user.name }}
  </li>
</ul>
data() {
  return {
    users: [
      { id: 1, name: 'An' },
      { id: 2, name: 'Binh' }
    ]
  }
}

v-for với object

Lặp qua property của object: (value, key, index).

<ul>
  <li v-for="(value, key, index) in product" :key="key">
    {{ index }}. {{ key }}: {{ value }}
  </li>
</ul>

Thứ tự lặp theo thứ tự key của Object.keys(), không đảm bảo ổn định tuyệt đối giữa các engine. Cần thứ tự cố định thì dùng mảng.

v-for với range

n chạy từ 1 đến số đó.

<span v-for="n in 5" :key="n">{{ n }}</span>

key là bắt buộc

Luôn gắn :key duy nhất và ổn định cho mỗi item. Vue dùng key để nhận diện và tái sử dụng đúng phần tử khi danh sách thay đổi.

<!-- ĐÚNG: key ổn định theo id -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>

Không dùng index làm key khi danh sách có thể thêm/xóa/sắp xếp lại. Index thay đổi theo vị trí, làm Vue map sai phần tử, gây lỗi state (vd giá trị input dính nhầm dòng).

<!-- SAI khi list thay đổi thứ tự: dùng index làm key -->
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>

Chỉ chấp nhận index làm key khi list tĩnh, không bao giờ đổi thứ tự.

v-for với template

Render nhiều phần tử cho mỗi vòng lặp mà không thêm thẻ bọc thừa:

<template v-for="item in items">
  <dt :key="`t-${item.id}`">{{ item.term }}</dt>
  <dd :key="`d-${item.id}`">{{ item.desc }}</dd>
</template>

Ở Vue 2, key đặt trên từng phần tử con bên trong <template>, không đặt trên <template>.

v-for với component

Truyền dữ liệu vào component con qua props. Component không tự biết về vòng lặp, phải truyền tường minh.

<card-item
  v-for="item in items"
  :key="item.id"
  :title="item.title"
  :image="item.image" />

Array change detection

Vue 2 theo dõi được các mutation method của mảng, tự cập nhật view:

push, pop, shift, unshift, splice, sort, reverse.

this.items.push({ id: 3, name: 'Cuong' }) // reactive, view cập nhật

Hai trường hợp Vue 2 KHÔNG phát hiện được:

// 1. Gán phần tử theo index
this.items[0] = newValue // KHÔNG reactive

// 2. Đổi length trực tiếp
this.items.length = 0 // KHÔNG reactive

Cách xử lý:

// Thay vì gán theo index
this.$set(this.items, 0, newValue)
// hoặc
this.items.splice(0, 1, newValue)

// Thay vì đổi length
this.items.splice(0) // xóa toàn bộ, reactive

Method không mutate (trả về mảng mới) như filter, map, concat, slice thì gán lại biến để cập nhật:

this.items = this.items.filter(i => i.active)

Tips cho AEM

Render list từ JSON do Sling Model cung cấp qua data attribute.

<!-- HTL: serialize list ra JSON -->
<div id="cards" data-cards="${model.cardsJson @ context='attribute'}"></div>
new Vue({
  el: '#cards',
  data() {
    return {
      cards: JSON.parse(this.$el.dataset.cards || '[]')
    }
  }
})
<!-- template Vue -->
<div class="cards" v-if="cards.length">
  <div class="card" v-for="card in cards" :key="card.id">
    <h3>{{ card.title }}</h3>
    <p>{{ card.summary }}</p>
  </div>
</div>
<p v-else>Chưa có dữ liệu</p>

Lưu ý key: dùng id từ JCR (vd path hoặc một field định danh) làm :key, tránh dùng index để giữ đúng state khi author thêm/xóa item trong dialog.