Skip to content

Instantly share code, notes, and snippets.

@azoom-t-d-tuan
Last active March 24, 2023 04:41
Show Gist options
  • Save azoom-t-d-tuan/6aac5aa4da47b5f4a93d2dfbc7e12b47 to your computer and use it in GitHub Desktop.
Save azoom-t-d-tuan/6aac5aa4da47b5f4a93d2dfbc7e12b47 to your computer and use it in GitHub Desktop.

Key cho v-for trong Vuejs

Chắc hẳn trong quá trình làm việc với Vuejs, mọi người không ít lần sử dụng v-for để xử lý các vòng lặp. Và có hai vấn đề mà khá nhiều người đang gặp phải khi set giá trị cho key trong v-for.

1. Hiểu sai về tính unique của key

Trong docs của Vue có đề cập: Children of the same common parent must have unique keys.

Nhiều người đang hiểu same parent ở đây có nghĩa là nằm trong cùng component, hay cùng một file .vue . Tuy nhiên, Vue chỉ yêu cầu các key khác nhau khi trong cùng một v-for , có nghĩa là việc hai element có cùng key trong cùng một file .vue là bình thường, không có vấn đề gì.

Chỉ trừ duy nhất một trường hợp tuy nằm ở các v-for khác nhau nhưng key vẫn phải khác nhau đó là khi các v-for đó siblings - nghĩa là chúng nằm cạnh nhau trong DOM, ví dụ:

<div v-for="(item, index) in items" :key="index">{{ item.text }}</div>
<div v-for="(product, index) in products" :key="index">{{ product.name }}</div>

Với case này, chúng ta có 2 cách xử lý:

  • Thay đổi format cho key, mà thông thường mọi người vẫn hay dùng nhất là thêm prefix cho các key để phân biệt chúng với nhau (kiểu: item-${index} product-${index} )
  • Wrap từng v-for này vào các thẻ div

2. Sử dụng index để set giá trị cho key một cách bừa bãi

Việc sử dụng index làm key cho v-for (như ví dụ ở trên) về cơ bản là "thừa" (mặc dù nó vẫn có thể giúp chúng ta qua được cảnh báo của Eslint), lý do là vì trong cơ chế mặc định v-for của Vue cũng đã sử dụng index để theo dõi sự thay đổi của v-for.

Lạm dụng index trong mọi trường hợp sẽ có thể gây ra cho chúng ta nhiều tình huống kỳ quặc và rất khó để debug.

Một ví dụ minh họa:

Chúng ta có một component TodoItem như sau:

<template>
  <div>
    <input type="checkbox" :id="item.id" v-model="isDone" />
    <label :for="item.id">{{ item.name }}</label>
  </div>
</template>
<script>
export default {
  props: {
    item: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      isDone: false,
    };
  },
};
</script>

Component này sẽ có nhận một prop là item, hiển thị name của item lên giao diện, và bên cạnh là một checkbox được reactive với isDone được khai báo trong data (với giá trị mặc định ban đầu là false)

Ở page bằng cách dùng một vòng v-for, ta đẩy từng phần tử của mảng vào component thông qua prop item. Ngoài ra chúng ta sẽ dùng index là key cho từng phần tử:

<todo-item v-for="todo in todos" :item="todo" :key="index"></todo-item>
<button @click="addNew()">Add new item</button>

Trong đó todos là một mảng các giá trị được khởi tạo ở data của page, và addNew là một method với nhiệm vụ là là thêm một giá trị mới vào đầu của mảng todos bằng method unshift:

data() {
  return {
    todos: [
      { id: 1, name: "Do the homework" },
      { id: 2, name: "Read a book" },
      { id: 3, name: "Clean the house" },
    ],
  }
},
methods: {
  addNew() {
    const newItem = { id: 4, name: "Buy food" };
    this.todos.unshift(newItem);
  }
}

Chúng ta sẽ được một giao diện như này:

alt text

Sau đó, chúng ta thực hiện tích vào các checkbox của list:

alt text

Tiếp theo, chúng ta thêm item mới bằng cách nhấn vào button Add new, kỳ vọng của hành động này sẽ là thêm mới item: Buy food lên đầu danh sách với ô checkbox ở trạng thái là uncheck. Tuy nhiên khi click vào button Add new thì kết quả chúng ta nhận được lại là:

alt text

Nguyên nhân chính là do cơ chế Tối ưu hóa các thay đổi với DOM và sử dụng lại các phần tử hiện có nhiều nhất có thể (hay còn gọi là cơ chế in-place patch).

Item mới Buy food được thêm vào đúng vị trí của item Do the homework, vì vậy thay vì một phần tử mới được tạo ra, Vue đã thực hiện "vá" nó bằng cập nhật lại nội dung của phần tử từ Do the homework thành Buy food. Đồng thời giữ nguyên giá trị isDonetrue như ở trạng thái cũ.

Trong khi đó phần tử Clean the house bị "đẩy" xuống cuối cùng - một vị trí mà trước đây chưa từng tồn tại trong DOM. Do đó, component TodoItem được khởi tạo lại với giá trị isDonefalse.

Để tránh xảy ra những tình huống như trên, chúng ta nên sử dụng ID để set key trong mọi trường hợp có thể, ví dụ:

<todo-item v-for="todo in todos" :item="todo" :key="todo.id"></todo-item>

id là duy nhất cho mỗi item, chúng ta đang "hiển thị" một cách hiệu quả thuật toán những mục nào đã có trước khi thay đổi và mục nào đã được thêm vào.

alt text

Nhiều người hỏi rằng họ luôn luôn phải tạo id? Đúng là đôi khi việc tạo id mới là thừa và có thể tránh được.

Sau đây là một số điều kiện:

  • Danh sách và các mục là static – chúng không được tính toán và không thay đổi.
  • Các mục trong danh sách không có id
  • Danh sách không bao giờ được lọc hoặc sắp xếp lại.

Khi nào tất cả các điều kiện trên được đáp ứng, bạn có thể sử dụng index làm key một cách an toàn.

Reference links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment