Vue Computed Properties
Vue Computed Properties
Diagram
1. Computed Properties Flow
┌─────────────────────────────────────────────────────────────┐
│ Reactive Dependencies │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ firstName │ │ lastName │ │
│ │ 'John' │ │ 'Doe' │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ └────────┬───────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Computed Getter │ │
│ │ firstName + │ │
│ │ ' ' + │ │
│ │ lastName │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ fullName │ │
│ │ 'John Doe' │ │
│ │ (cached) │ │
│ └────────┬────────┘ │
└──────────────────┼─────────────────────────────────────────┘
│
▼
Template Usage
{{ fullName }}
2. Computed vs Methods
┌──────────────────────────────────────────────────────────────┐
│ COMPUTED PROPERTY │
├──────────────────────────────────────────────────────────────┤
│ │
│ First Access: │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Call │ -> │ Run Getter │ -> │ Cache │ │
│ │ Computed │ │ Function │ │ Result │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ │
│ │
│ Subsequent Access (dependencies unchanged): │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Call │ -> │ Return │ │
│ │ Computed │ │ Cached │ │
│ └─────────────┘ └──────────────┘ │
│ ✓ Fast! │
│ │
│ When Dependency Changes: │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Dependency │ -> │ Invalidate │ -> │ Re-compute │ │
│ │ Changed │ │ Cache │ │ on Access │ │
│ └─────────────┘ └──────────────┘ └─────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ METHOD │
├──────────────────────────────────────────────────────────────┤
│ │
│ Every Access: │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Call │ -> │ Run Method │ │
│ │ Method │ │ Function │ │
│ └─────────────┘ └──────────────┘ │
│ │
│ No Caching - Always executes │
│ │
│ ┌─────────────┐ ┌──────────────┐ │
│ │ Call │ -> │ Run Method │ │
│ │ Again │ │ Again │ │
│ └─────────────┘ └──────────────┘ │
│ ✗ Slow │
│ │
└──────────────────────────────────────────────────────────────┘
3. Writable Computed
┌─────────────────────────────────────────────────────────────┐
│ Writable Computed Property │
└─────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ READ (Getter) │
├──────────────────────────────────────────────────────────────┤
│ │
│ Access: fullName.value │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ get() { │ │
│ │ return │ │
│ │ firstName + │ │
│ │ ' ' + │ │
│ │ lastName │ │
│ │ } │ │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ 'John Doe' │
│ │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ WRITE (Setter) │
├──────────────────────────────────────────────────────────────┤
│ │
│ Assignment: fullName.value = 'Jane Smith' │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ set(newValue) { │ │
│ │ [firstName.value, lastName.value]│ │
│ │ = newValue.split(' ') │ │
│ │ } │ │
│ └────────┬────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────┐ │
│ │ firstName.value = 'Jane' │ │
│ │ lastName.value = 'Smith' │ │
│ └────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
4. Dependency Tracking
┌─────────────────────────────────────────────────────────────┐
│ Automatic Dependency Tracking │
└─────────────────────────────────────────────────────────────┘
Step 1: Define Computed
┌─────────────────────────────────────┐
│ const total = computed(() => { │
│ return price.value * quantity.value│
│ }) │
└─────────────────────────────────────┘
│
▼
Step 2: First Access - Track Dependencies
┌─────────────────────────────────────┐
│ Template reads │
│ │
│ Getter runs: │
│ - Access price.value ← Track! │
│ - Access quantity.value ← Track! │
│ │
│ Dependencies: [price, quantity] │
└─────────────────────────────────────┘
│
▼
Step 3: Cache Result
┌─────────────────────────────────────┐
│ total.value = 100 │
│ (cached) │
└─────────────────────────────────────┘
│
▼
Step 4: Dependency Change
┌─────────────────────────────────────┐
│ price.value = 50 (was 10) │
│ │ │
│ ▼ │
│ Notify: total is dirty! │
│ Invalidate cache │
└─────────────────────────────────────┘
│
▼
Step 5: Re-compute on Next Access
┌─────────────────────────────────────┐
│ accessed again │
│ │ │
│ ▼ │
│ Run getter again │
│ Cache new result: 250 │
└─────────────────────────────────────┘
5. Performance Comparison
┌─────────────────────────────────────────────────────────────┐
│ Expensive Computation Example │
└─────────────────────────────────────────────────────────────┘
Scenario: Filter large array (10,000 items)
With METHOD:
┌──────────────────────────────────────┐
│ Component Re-render Triggered │
│ │
│ Template: │
│ ← Run │
│ ← Run again │
│ ← Run again │
│ │
│ Each call: Loop 10,000 items │
│ Total: 30,000 iterations! │
│ Time: ~300ms │
└──────────────────────────────────────┘
With COMPUTED:
┌──────────────────────────────────────┐
│ Component Re-render Triggered │
│ │
│ Template: │
│ ← Run │
│ ← Cached ✓ │
│ ← Cached ✓ │
│ │
│ First call: Loop 10,000 items │
│ Total: 10,000 iterations │
│ Time: ~10ms │
└──────────────────────────────────────┘
Tổng quan
- Computed properties = derived state từ reactive dependencies.
- Tự động cache kết quả → performance tốt hơn methods.
- Tự động track dependencies và update khi cần.
- Nên dùng cho logic phức tạp trong template.
Basic Example
Vấn đề với Template Expression
<template>
<!-- Logic phức tạp trong template -->
<span>{{ author.books.length > 0 ? "Yes" : "No" }}</span>
</template>
Nhược điểm:
- Template phức tạp, khó đọc
- Khó maintain
- Lặp lại logic nếu dùng nhiều nơi
Giải pháp: Computed Property
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? "Yes" : "No";
});
<template>
<span>{{ publishedBooksMessage }}</span>
</template>
Ưu điểm:
- Template gọn, dễ đọc
- Logic tách riêng
- Tái sử dụng dễ dàng
- Auto cache
Computed Caching vs Methods
So sánh
| Tiêu chí | Computed | Methods |
|---|---|---|
| Caching | Có ✓ | Không ✗ |
| Re-run | Chỉ khi dependency thay đổi | Mỗi lần re-render |
| Performance | Tốt (cached) | Chậm hơn |
| Use case | Derived state | Actions, side effects |
Ví dụ
Computed (Cached):
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? "Yes" : "No";
});
// Gọi nhiều lần trong cùng render cycle
publishedBooksMessage.value; // Run getter
publishedBooksMessage.value; // Return cached ✓
publishedBooksMessage.value; // Return cached ✓
Method (No Cache):
function calculateBooksMessage() {
return author.books.length > 0 ? "Yes" : "No";
}
// Mỗi lần gọi đều chạy lại
calculateBooksMessage(); // Run function
calculateBooksMessage(); // Run function again ✗
calculateBooksMessage(); // Run function again ✗
Khi nào computed KHÔNG update?
// KHÔNG update vì Date.now() không phải reactive dependency
const now = computed(() => Date.now());
// Luôn trả về giá trị cũ
Writable Computed
Mặc định: Read-only
const fullName = computed(() => {
return firstName.value + " " + lastName.value;
});
// Không thể gán
fullName.value = "Jane Doe"; // ⚠️ Runtime warning!
Tạo Writable Computed
const fullName = computed({
// Getter
get() {
return firstName.value + " " + lastName.value;
},
// Setter
set(newValue) {
[firstName.value, lastName.value] = newValue.split(" ");
},
});
// Có thể đọc
console.log(fullName.value); // 'John Doe'
// Có thể ghi
fullName.value = "Jane Smith";
// firstName.value = 'Jane'
// lastName.value = 'Smith'
Use Case
- Two-way binding với complex state
- Normalize input data
- Sync multiple reactive values
Getting Previous Value (Vue 3.4+)
Read-only Computed
const alwaysSmall = computed((previous) => {
if (count.value <= 3) {
return count.value;
}
// Giữ giá trị cũ nếu count > 3
return previous;
});
// count = 2 → alwaysSmall = 2
// count = 3 → alwaysSmall = 3
// count = 4 → alwaysSmall = 3 (previous)
// count = 5 → alwaysSmall = 3 (previous)
Writable Computed
const alwaysSmall = computed({
get(previous) {
if (count.value <= 3) {
return count.value;
}
return previous;
},
set(newValue) {
count.value = newValue * 2;
},
});
Best Practices
1. Getters Should Be Side-Effect Free
❌ Không nên:
const badComputed = computed(() => {
// Mutate state
otherState.value++;
// Make API call
fetch("/api/data");
// Mutate DOM
document.title = "New Title";
return someValue;
});
✓ Nên:
const goodComputed = computed(() => {
// Pure computation only
return data.value * 2;
});
2. Avoid Mutating Computed Value
❌ Không nên:
const filteredItems = computed(() => {
return items.value.filter((item) => item.active);
});
// KHÔNG mutate computed value
filteredItems.value.push(newItem); // ✗
✓ Nên:
// Update source state
items.value.push(newItem); // ✓
3. Computed vs Watchers
Dùng Computed khi:
- Derive value từ reactive state
- Synchronous transformation
- Cần cache result
Dùng Watchers khi:
- Side effects (API calls, logging)
- Asynchronous operations
- Mutate state dựa trên change
4. Keep Computed Simple
❌ Quá phức tạp:
const complexComputed = computed(() => {
let result = 0;
for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
result += someCalculation(i, j);
}
}
return result;
});
✓ Tách logic:
function expensiveCalculation(data) {
// Complex logic here
return result;
}
const simpleComputed = computed(() => {
return expensiveCalculation(data.value);
});
Common Use Cases
1. Filtering Lists
const activeUsers = computed(() => {
return users.value.filter((user) => user.active);
});
2. Sorting
const sortedItems = computed(() => {
return [...items.value].sort((a, b) => a.price - b.price);
});
3. Formatting
const formattedDate = computed(() => {
return new Date(date.value).toLocaleDateString();
});
4. Aggregation
const total = computed(() => {
return cart.value.reduce((sum, item) => {
return sum + item.price * item.quantity;
}, 0);
});
5. Conditional Logic
const canSubmit = computed(() => {
return form.value.email && form.value.password && !loading.value;
});
Kết luận
- Computed properties = cached derived state.
- Tự động track dependencies và update.
- Performance tốt hơn methods nhờ caching.
- Getters phải pure và side-effect free.
- Không mutate computed value, update source state.
- Dùng cho synchronous transformations.
- Watchers cho side effects và async operations.