+67

Bài 5: Sử dụng computed trong VueJS, sự khác nhau giữa computed và methods

Mình đã cập nhật lại tất cả các bài với các thay đổi ở hiện tại ở năm 2024: Vue 3, Vite, Laravel 10x,...

Chào mừng các bạn quay trở lại với series học VueJS của mình. Ở bài trước chúng ta đã tìm hiểu cách sử dụng của methods trong Vue, bài này chúng ta sẽ tìm hiểu về computed, cách sử dụng và so sánh sự khác nhau giữa computedmethods

Computed là gì và cách sử dụng

Tổng quan

Ta bắt đầu bài này với ví dụ sau nhé:

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

const age = ref(16)

function updateAge() {
  age.value = 50
}
</script>

<template>
  <div class="my-component">{{ age >= 50 ? 'Senior' : age > 25 && age < 50 ? 'Adult' : 'Young' }}</div>
  <button @click="updateAge">
    Update
  </button>
</template>

Ở trên ta có 1 reactive state là 1 cái ref để hiển thị tuổi, giá trị ban đầu là 16, click vào button sẽ update tuổi thành 50.

Và ta hiển thị tuổi ra với logics như sau:

  • nếu tuổi >= 50, hiển thị Senior
  • nếu > 25 và < 50, hiển thị Adult
  • Còn lại thì hiển thị Young ( <= 25)

Code của chúng ta chạy bình thường

Vậy nhưng nếu các bạn để ý thì phần template logics nom có vẻ hơi lộn xộn và dài dòng, code UI nên tập trung nhiều về UI đẩy phần xử lý về phía JS (script).

Và trong những trường hợp như thế này thì là ta sẽ dùng tới computed property, nó thực tế cũng "giống" như 1 reactive state được tính toán dựa trên những reactive state mà ta khai báo từ trước. Cùng sửa lại code một chút nhé:

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

const age = ref(16)

const msg = computed(() => {
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
})

function updateAge() {
  age.value = 50
}
</script>

<template>
  <div class="my-component">{{ msg }}</div>
  <button @click="updateAge">
    Update
  </button>
</template>

Ở trên ta khai báo mới 1 cái msgcomputed, và computed thì phải return về 1 giá trị nào đó đó nhé 😃

Quay lại trình duyệt F5 và ta sẽ được kết quả tương tự.

Vọc vạch

Ở trên ta thấy rằng computed nhận vào là 1 function và return về 1 giá trị nào đó.

Computed sẽ tự "theo dõi" các reactive state bên trong nó và tự tính toán lại nếu như có bất kì reactive state nào thay đổi.

Và nếu ta console.log(msg) thì sẽ thấy rằng nó thực tế là 1 cái ComputedRef. Mà đã là ref thì khi truy cập ở phần <script> là ta phải dùng tới .value đó nhé:

const msg = computed(() => {
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
})

console.log(msg.value)

Ta cũng có thể tạo mới 1 computed mà giá trị của nó được tính toán dựa vào 1 computed khác, ví dụ:

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

const age = ref(16)

const msg = computed(() => {
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
})

const newMsg = computed(() => {
  return `Hello ${msg.value}`
})

function updateAge() {
  age.value = 50
}
</script>

<template>
  <div class="my-component">{{ msg }}</div>
  <h1>{{ newMsg }}</h1>
  <button @click="updateAge">
    Update
  </button>
</template>

Ở đây ta có newMsg là 1 cái computed mới, giá trị được tính toán bằng msg. F5 lại và ta sẽ thấy kết quả như sau:

Screenshot 2023-12-26 at 4.59.19 PM.png

Sự khác nhau giữa computed và methods

Các bạn có thể thắc mắc là, vậy thì ta cũng có thể dùng 1 method (function) và làm y như computed được mà đúng không nhỉ? 🤔🤔

Ví dụ:

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

const age = ref(16)

function updateAge() {
  age.value = 50
}

function msg() {
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
}

</script>

<template>
  <div class="my-component">{{ msg() }}</div>
  <button @click="updateAge">
    Update
  </button>
</template>

Nếu ta chạy đoạn code trên thì cho ra kết quả tương tự, mà nom rõ ràng là ngắn hơn 🧐🧐🧐🧐

Thì điểm khác biệt lớn nhất của computed propertymethodscomputed sẽ chỉ được tính toán lại nếu như bất kì reactive state nào bên trong nó thay đổi. Tức là nếu ta có gọi tới computed nhiều lần đi chăng nữa thì computed cũng sẽ ngay lập tức trả về nếu không có state nào thay đổi, nó như kiểu cache đó các bạn 😉. Trong khi methods sẽ luôn luôn được thực hiện lại mỗi khi re-render (mỗi khi có bất kì reactive state nào của component thay đổi).

Điều này dẫn tới việc methods có thể bị chạy lại rất nhiều lần và ảnh hưởng tới performance.

Ta thử test xem nhé, sửa lại code như sau:

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

const age = ref(16)
const number = reactive({ count: 0 })

function updateAge() {
  age.value = 50
}

function msg() {
  console.log(1)
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
}

function updateNumber() {
  number.count = parseInt(Math.random() * 100)
}

</script>

<template>
  <div class="my-component">{{ msg() }}</div>
  <h1>{{ number.count }}</h1>
  <button @click="updateAge">
    Update age
  </button>
  <button @click="updateNumber">
    Update number
  </button>
</template>

Ở trên ta tạo thêm 1 reactive state mới là number, và thêm 1 button, click vào button sẽ update number.count thành 1 giá trị random. Cùng với đó là ta để 1 cái console.log trong function msg, để xem nó chạy bao nhiêu lần. Quay trở lại trình duyệt, F5 và click nhiều lần vào nút Update number nhé:

Screenshot 2023-12-26 at 5.10.54 PM.png

Như các bạn thấy, cứ khi nào number.count thay đổi -> component re-render -> function msg lại chạy lại, trong khi giá trị trả về của msg không hề thay đổi, ví dụ mà bên trong msg ta thực hiện 1 tỉ logics thì sẽ tốn performance như thế nào 😉

Giờ ta refactor lại chút dùng computed thay vì methods nhé:

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

const age = ref(16)
const number = reactive({ count: 0 })

function updateAge() {
  age.value = 50
}

const msg = computed(() => {
  console.log(1)
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'

})

function updateNumber() {
  number.count = parseInt(Math.random() * 100)
}

</script>

<template>
  <div class="my-component">{{ msg }}</div>
  <h1>{{ number.count }}</h1>
  <button @click="updateAge">
    Update age
  </button>
  <button @click="updateNumber">
    Update number
  </button>
</template>

Sau đó quay trở lại trình duyệt F5 và bấm Update number nhiều lần, ta sẽ thấy rằng computed msg chỉ thực hiện 1 lần, vì age không hề thay đổi 🚀🚀🚀

Nếu bạn nào đã dùng React thì computed ở đây khá giống với useMemo

Một chú ý quan trọng nữa là, vì computed nó chỉ "theo dõi" khi có reactive state nào đó thay đổi và thực thi lại thôi, nên nếu bên trong computed mà ta chả có cái reactive state nào thì computed sẽ không bao giờ chạy lại, ví dụ:

const now = computed(() => Date.now())

Writable computed

Từ đầu bài đến giờ các bạn để ý rằng ta chỉ sử dụng giá trị trả về của computed và hiển thị , vậy có khi nào ta thắc mắc rằng liệu ta có thể thay đổi computed không? Vì nó là ref mà.

Nếu ta gọi msg.value = 'abcxyz' thì sao? 🤔🤔

Thì nếu ta gọi msg.value = 'abcxyz' sẽ bị warning là Write operation failed: computed value is readonly, vì computed của ta hiện tại chỉ là readonly, chứ chưa write vào nó được.

Ta cập nhật lại 1 chút như sau nhé:

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

const age = ref(16)

function updateAge() {
  age.value = 50
}

const msg = computed({
  get() {
    return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
  },
  set(value) {
    age.value = value * 2
  }
})

function updateComputed() {
  msg.value = 30
}

</script>

<template>
  <div class="my-component">{{ msg }}</div>
  <h1>Age: {{ age }}</h1>
  <button @click="updateAge">
    Update age
  </button>
  <button @click="updateComputed">
    Update computed
  </button>
</template>

Ở trên các bạn thấy là ta phải sửa lại chút chỗ khai báo computed với getset, và sau đó ta có thể update computed như ref bình thường với .value. Các bạn tự test nhé 😃

Theo trải nghiệm của mình thì rất rất rất ít khi ta cần phải write vào computed

Chú ý

Có đôi điều chú ý cho các bạn khi sử dụng computed:

  • Không nên để side effect trong computed: ví dụ như call API, hay thực vụ các tác vụ async, hay là thao tác với DOM (document.findElementById.innerHTML =....). Trong computed ta nên chỉ để các tính toán đơn thuần (nếu cần thực hiện side effect khi reactive state thay đổi thì ta thường dùng watcher - sẽ được nói ở các bài sau)
  • Hạn chế thay đổi giá trị của computed, như mình đã note ở phần trước, rất ít khi ta cần tới writable computed, nếu cần computed update thì ta đơn giản là update bất kì reactive state nào đó trong computed là được rồi, ví dụ:
// Thay vì
const msg = computed({
  get() {
    return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
  },
  set(value) {
    age.value = value * 2
  }
})

function updateComputed() {
  msg.value = 30
}

// Ta nên:

const msg = computed(() => {
  return age.value >= 50 ? 'Senior' : age.value > 25 && age.value < 50 ? 'Adult' : 'Young'
})

function updateAge() {
  age.value = 60
}

Vue 2

Với Vue 2 thì ví dụ tương tự cho bài này sẽ là:

<script>
export default {
  data() {
    return {
      age: 16
    }
  },
  computed: {
    msg() {
      return this.age >= 50 ? 'Senior' : this.age > 25 && this.age < 50 ? 'Adult' : 'Young'
    }
  },
  methods: {
    updateAge() {
      this.age = 60
    }
  }
}

</script>

<template>
  <div class="my-component">{{ msg }}</div>
  <button @click="updateAge">
    Update age
  </button>
</template>

Ở trên ta khai báo computed với msg() bên trong, cú pháp tương tự như khi ta khai báo methods vậy

Kết luận

Kiến thức trong bài mình tổng hợp từ những gì mình học được và mình thấy cần thiết khi xây dựng dự án thực tế, hi vọng các bạn đã hiểu về computed property và cách sử dụng nó. 😃

Ở bài tiếp theo chúng ta sẽ tìm hiểu về watcher trong VueJS nhé.

Cám ơn các bạn đã theo dõi ^^ !


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí