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 11x,...
Cập nhật gần nhất 07/06/2024
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 computed
và methods
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 msg
là computed
, 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:
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 property
và methods
là computed
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é:
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ớiuseMemo
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 get
và set
, 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ùngwatcher
- 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