Skip to content

Các Thuộc Tính Tính Toán

Ví Dụ Cơ Bản

Các biểu thức trong mẫu rất thuận tiện, nhưng chúng được thiết kế cho các hoạt động đơn giản. Đặt quá nhiều logic trong mẫu có thể làm cho chúng trở nên rối bời và khó bảo trì. Ví dụ, nếu chúng ta có một đối tượng với một mảng lồng:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  }
}
js
const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

Và chúng ta muốn hiển thị các thông báo khác nhau tùy thuộc vào việc author đã có sách hay chưa:

template
<p>Đã xuất bản sách:</p>
<span>{{ author.books.length > 0 ? 'Có' : 'Không' }}</span>

Ở điểm này, mẫu trở nên hơi lộn xộn. Chúng ta phải nhìn vào nó một lúc trước khi nhận ra rằng nó thực hiện một phép tính tùy thuộc vào author.books. Quan trọng hơn, chúng ta có thể không muốn lặp lại nếu chúng ta cần bao gồm phép tính này trong mẫu nhiều hơn một lần.

Chính vì vậy, đối với logic phức tạp bao gồm dữ liệu phản ứng, nên sử dụng một thuộc tính tính toán. Dưới đây là ví dụ tương tự, được tái cấu trúc:

js
export default {
  data() {
    return {
      author: {
        name: 'John Doe',
        books: [
          'Vue 2 - Advanced Guide',
          'Vue 3 - Basic Guide',
          'Vue 4 - The Mystery'
        ]
      }
    }
  },
  computed: {
    // một getter tính toán
    messageSachDaXuatBan() {
      // `this` trỏ đến thể hiện của component
      return this.author.books.length > 0 ? 'Có' : 'Không'
    }
  }
}
template
<p>Đã xuất bản sách:</p>
<span>{{ messageSachDaXuatBan }}</span>

Thử nghiệm ở Playground

Ở đây, chúng ta đã khai báo một thuộc tính tính toán messageSachDaXuatBan.

Hãy thử thay đổi giá trị của mảng books trong dữ liệu ứng dụng và bạn sẽ thấy cách messageSachDaXuatBan thay đổi tương ứng.

Bạn có thể kết nối dữ liệu với các thuộc tính tính toán trong mẫu giống như một thuộc tính bình thường. Vue nhận biết rằng this.messageSachDaXuatBan phụ thuộc vào this.author.books, nên nó sẽ cập nhật mọi kết nối phụ thuộc vào this.messageSachDaXuatBan khi this.author.books thay đổi.

Xem thêm: Gõ Thuộc Tính Tính Toán

vue
<script setup>
import { reactive, computed } from 'vue'

const author = reactive({
  name: 'John Doe',
  books: [
    'Vue 2 - Advanced Guide',
    'Vue 3 - Basic Guide',
    'Vue 4 - The Mystery'
  ]
})

// một ref tính toán
const messageSachDaXuatBan = computed(() => {
  return author.books.length > 0 ? 'Có' : 'Không'
})
</script>

<template>
  <p>Đã xuất bản sách:</p>
  <span>{{ messageSachDaXuatBan }}</span>
</template>

Thử nghiệm ở Playground

Ở đây, chúng ta đã khai báo một thuộc tính tính toán messageSachDaXuatBan. Hàm computed() mong đợi được truyền một hàm getter, và giá trị trả về là một ref tính toán. Tương tự như ref bình thường, bạn có thể truy cập kết quả tính toán như là messageSachDaXuatBan.value. Refs tính toán cũng tự động mở gói trong mẫu để bạn có thể tham chiếu đến chúng mà không cần .value trong biểu thức mẫu.

Một thuộc tính tính toán tự động theo dõi các phụ thuộc phản ứng của nó. Vue nhận biết rằng việc tính toán messageSachDaXuatBan phụ thuộc vào author.books, nên nó sẽ cập nhật mọi kết nối phụ thuộc vào messageSachDaXuatBan khi author.books thay đổi.

Xem thêm: Gõ Tính Toán

Bộ Nhớ Đệm Của Thuộc Tính Tính Toán So Với Phương Thức

Bạn có thể đã nhận thức được rằng chúng ta có thể đạt được kết quả tương tự bằng cách gọi một phương thức trong biểu thức:

template
<p>{{ calculateBooksMessage() }}</p>
js
// trong component
methods: {
  calculateBooksMessage() {
    return this.author.books.length > 0 ? 'Có' : 'Không'
  }
}
js
// trong component
function calculateBooksMessage() {
  return author.books.length > 0 ? 'Có' : 'Không'
}

Thay vì sử dụng một thuộc tính tính toán, chúng ta có thể định nghĩa cùng một hàm dưới dạng một phương thức. Đối với kết quả cuối cùng, hai phương pháp này đều hoàn toàn giống nhau. Tuy nhiên, sự khác biệt là các thuộc tính tính toán được lưu vào bộ nhớ đệm dựa trên các phụ thuộc phản ứng của chúng. Một thuộc tính tính toán chỉ sẽ đánh giá lại khi một số phụ thuộc phản ứng của nó đã thay đổi. Điều này có nghĩa là miễn là author.books không thay đổi, nhiều lần truy cập đến publishedBooksMessage sẽ ngay lập tức trả lại kết quả tính toán trước đó mà không cần chạy lại hàm getter.

Điều này cũng có nghĩa là thuộc tính tính toán sau sẽ không bao giờ cập nhật, vì Date.now() không phải là một phụ thuộc phản ứng:

js
computed: {
  now() {
    return Date.now()
  }
}
js
const now = computed(() => Date.now())

So với đó, việc gọi một phương thức sẽ luôn luôn chạy hàm mỗi khi một lần render xảy ra.

Tại sao chúng ta cần sử dụng bộ nhớ đệm? Hãy tưởng tượng rằng chúng ta có một thuộc tính tính toán đắt tiền list, yêu cầu lặp qua một mảng lớn và thực hiện nhiều phép tính. Sau đó, có thể có các thuộc tính tính toán khác phụ thuộc vào list. Nếu không có bộ nhớ đệm, chúng ta sẽ thực thi getter của list nhiều lần hơn là cần thiết! Trong những trường hợp nơi bạn không muốn sử dụng bộ nhớ đệm, hãy sử dụng gọi một phương thức thay vào đó.

Thuộc Tính Tính Toán Có Thể Ghi

Thuộc tính tính toán mặc định chỉ có thể đọc. Nếu bạn cố gắng gán một giá trị mới cho một thuộc tính tính toán, bạn sẽ nhận được một cảnh báo tại thời điểm chạy. Trong những trường hợp hiếm hoi khi bạn cần một thuộc tính tính toán "có thể ghi", bạn có thể tạo một bằng cách cung cấp cả hàm getter và hàm setter:

js
export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    }
  },
  computed: {
    fullName: {
      // getter
      get() {
        return this.firstName + ' ' + this.lastName
      },
      // setter
      set(newValue) {
        // Lưu ý: chúng ta sử dụng cú pháp gán hủy ở đây.
        ;[this.firstName, this.lastName] = newValue.split(' ')
      }
    }
  }
}

Bây giờ khi bạn chạy this.fullName = 'John Doe', hàm setter sẽ được gọi và this.firstNamethis.lastName sẽ được cập nhật tương ứng.

vue
<script setup>
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  // getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // setter
  set(newValue) {
    // Lưu ý: chúng ta sử dụng cú pháp gán hủy ở đây.
    ;[firstName.value, lastName.value] = newValue.split(' ')
  }
})
</script>

Bây giờ khi bạn chạy fullName.value = 'John Doe', hàm setter sẽ được gọi và firstNamelastName sẽ được cập nhật tương ứng.

Thực Hành Tốt Nhất

Getter Nên Là Không Tác Động Phụ

Quan trọng nhớ rằng các hàm getter của thuộc tính tính toán chỉ nên thực hiện tính toán tinh khiết và không tác động phụ. Ví dụ, **đừng thay đổi trạng thái khác, thực hiện yêu cầu không đồng bộ, hoặc thay đổi DOM bên trong một hà

m getter của thuộc tính tính toán!** Hãy coi thuộc tính tính toán như là mô tả một cách tuyên bố làm thế nào để suy ra một giá trị dựa trên các giá trị khác - trách nhiệm của nó chỉ nên là tính toán và trả về giá trị đó. Sau này trong hướng dẫn, chúng ta sẽ thảo luận về cách chúng ta có thể thực hiện các tác động phụ khi có thay đổi trạng thái với watchers.

Tránh Việc Thay Đổi Giá Trị Tính Toán

Giá trị trả về từ một thuộc tính tính toán là trạng thái suy ra. Hãy coi nó như là một bức ảnh tạm thời - mỗi khi trạng thái nguồn thay đổi, một bức ảnh mới được tạo ra. Không có ý nghĩa gì khi thay đổi một bức ảnh, nên một giá trị trả về từ tính toán nên được coi là chỉ có thể đọc và không bao giờ được thay đổi - thay vào đó, hãy cập nhật trạng thái nguồn nó phụ thuộc vào để kích thích tính toán mới.

Các Thuộc Tính Tính Toán has loaded