Vue.js là một framework JavaScript lũy tiến để xây dựng giao diện người dùng, và trong quá trình phát triển ứng dụng Vue, bạn sẽ thường xuyên bắt gặp hai khái niệm quan trọng là methods và computed. Mặc dù cả hai đều là các tùy chọn để xử lý logic trong component, chúng có những điểm khác biệt cơ bản về cách hoạt động, hiệu suất và trường hợp sử dụng tối ưu. Hiểu rõ sự khác biệt này không chỉ giúp bạn viết code Vue hiệu quả hơn mà còn tối ưu hóa hiệu suất ứng dụng.
1. methods: Hàm Xử Lý Sự Kiện và Logic Riêng Biệt
methods là một đối tượng chứa các hàm JavaScript thông thường mà bạn có thể định nghĩa trong options của Vue component. Chúng được sử dụng để thực hiện các tác vụ khi một sự kiện cụ thể xảy ra (ví dụ: click chuột, submit form) hoặc khi bạn cần một chức năng có thể được gọi tường minh.
Cách hoạt động:
- Thực thi theo yêu cầu: Mỗi khi một
method được gọi (ví dụ: thông qua @click trong template hoặc this.myMethod() trong script), toàn bộ hàm sẽ được thực thi lại từ đầu. Vue không lưu trữ kết quả của methods.
- Không có bộ nhớ đệm (caching): Nếu
method của bạn thực hiện một phép tính phức tạp hoặc gọi API, nó sẽ thực hiện lại phép tính đó mỗi khi được gọi, ngay cả khi các dependencies của nó không thay đổi.
- Truy cập
this: Bên trong một method, bạn có thể truy cập các thuộc tính dữ liệu (data), thuộc tính tính toán (computed) và các methods khác của component thông qua this.
Trường hợp sử dụng phổ biến:
- Xử lý sự kiện (event handlers): Ví dụ:
@click="incrementCounter", @submit="saveForm".
- Thực hiện các thao tác có tác dụng phụ (side effects): Cập nhật dữ liệu, gọi API, điều hướng.
- Các hàm không trả về giá trị hoặc trả về giá trị mà không cần ghi nhớ.
- Khi bạn cần truyền tham số vào hàm.
Ví dụ về methods:
<template>
<div>
<p>Số lượng: {{ counter }}</p>
<button @click="increment">Tăng</button>
<button @click="reset(0)">Đặt lại</button>
</div>
</template>
<script>
export default {
data() {
return {
counter: 0
};
},
methods: {
increment() {
this.counter++;
console.log('Counter tăng lên:', this.counter);
},
reset(value) {
this.counter = value;
console.log('Counter đã được đặt lại về:', this.counter);
}
}
};
</script>
Trong ví dụ trên, increment và reset là các methods. Mỗi khi nút được nhấn, hàm tương ứng sẽ được gọi và thực thi.
2. computed: Thuộc Tính Tính Toán Có Bộ Nhớ Đệm
computed là một đối tượng chứa các thuộc tính được tính toán dựa trên các thuộc tính dữ liệu (reactive dependencies) khác trong component. computed hoạt động như một thuộc tính, nhưng giá trị của nó được tính toán một cách động và được lưu vào bộ nhớ đệm.
Cách hoạt động:
- Tính toán dựa trên dependencies: Một
computed property sẽ chỉ được đánh giá lại khi bất kỳ dependency reactive nào của nó thay đổi. Nếu các dependencies không thay đổi, nó sẽ trả về giá trị đã được lưu trong bộ nhớ đệm mà không cần thực hiện lại phép tính.
- Có bộ nhớ đệm (caching): Đây là điểm khác biệt cốt lõi. Vue theo dõi các dependencies của
computed property. Khi bạn truy cập một computed property, Vue kiểm tra xem các dependencies của nó có thay đổi hay không. Nếu không, nó sẽ trả về giá trị đã được lưu mà không cần thực hiện lại hàm. Điều này giúp tối ưu hóa hiệu suất đáng kể, đặc biệt với các phép tính phức tạp.
- Hoạt động như một getter:
computed property về cơ bản là một getter. Bạn truy cập nó như một thuộc tính dữ liệu thông thường, không cần gọi với dấu ngoặc đơn (()).
- Không có tác dụng phụ:
computed properties nên là các hàm thuần túy, nghĩa là chúng chỉ tính toán và trả về một giá trị, không gây ra bất kỳ tác dụng phụ nào (như sửa đổi dữ liệu bên ngoài, gọi API).
- Truy cập
this: Tương tự methods, bạn có thể truy cập các thuộc tính dữ liệu, thuộc tính tính toán và methods khác thông qua this.
Trường hợp sử dụng phổ biến:
- Chuyển đổi hoặc định dạng dữ liệu hiện có để hiển thị.
- Lọc hoặc sắp xếp danh sách dữ liệu.
- Tính toán giá trị tổng hợp từ nhiều thuộc tính dữ liệu.
- Khi bạn cần một giá trị được cập nhật tự động khi các dependencies của nó thay đổi, và muốn tối ưu hóa hiệu suất bằng cách sử dụng bộ nhớ đệm.
Ví dụ về computed:
<template>
<div>
<input type="text" v-model="firstName" placeholder="Tên">
<input type="text" v-model="lastName" placeholder="Họ">
<p>Họ và tên đầy đủ: {{ fullName }}</p>
<p>Tên viết hoa: {{ capitalizedFirstName }}</p>
</div>
</template>
<script>
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
};
},
computed: {
fullName() {
console.log('Tính toán fullName...'); // Chỉ log khi firstName hoặc lastName thay đổi
return `${this.firstName} ${this.lastName}`;
},
capitalizedFirstName() {
console.log('Tính toán capitalizedFirstName...'); // Chỉ log khi firstName thay đổi
return this.firstName.toUpperCase();
}
}
};
</script>
Trong ví dụ này, fullName và capitalizedFirstName là các computed properties. fullName sẽ chỉ được tính toán lại khi firstName hoặc lastName thay đổi. Tương tự, capitalizedFirstName chỉ tính toán lại khi firstName thay đổi. Nếu bạn thay đổi một trường input, bạn sẽ thấy log console.log tương ứng chỉ chạy một lần, chứng minh khả năng caching của computed.
3. Bảng So Sánh Chi Tiết methods vs computed
| Tiêu chí | methods | computed |
| :------------------- | :--------------------------------------- | :-------------------------------------------- |
| Mục đích chính | Xử lý sự kiện, thực thi logic độc lập. | Tính toán và trả về giá trị dẫn xuất (derived value). |
| Cách gọi | Phải được gọi tường minh với dấu ngoặc đơn () (ví dụ: myMethod()). | Được truy cập như một thuộc tính (myComputedProperty), không cần (). |
| Bộ nhớ đệm (Caching) | Không có bộ nhớ đệm. Luôn thực thi lại mỗi khi được gọi. | Có bộ nhớ đệm. Chỉ thực thi lại khi các dependencies của nó thay đổi. |
| Phạm vi sử dụng | Xử lý sự kiện, tác vụ phức tạp, gọi API. | Định dạng, lọc, tổng hợp dữ liệu để hiển thị. |
| Dependencies | Không theo dõi dependencies một cách tự động để quyết định thực thi. | Tự động theo dõi các reactive dependencies và chỉ thực thi lại khi chúng thay đổi. |
| Tác dụng phụ | Có thể gây ra tác dụng phụ (thay đổi trạng thái, gọi API). | Nên là các hàm thuần túy, không gây tác dụng phụ. |
| Tham số | Có thể nhận tham số. | Không nhận tham số trực tiếp (nhưng có thể dùng hàm trả về hàm). |
| Đọc/Ghi | Thường để thực thi logic hoặc thay đổi trạng thái (ghi). | Chủ yếu để đọc giá trị (getter). Có thể có setter nhưng ít dùng. |
4. Khi nào nên dùng methods và khi nào nên dùng computed?
Sử dụng methods khi:
- Bạn cần thực hiện một hành động cụ thể khi người dùng tương tác với giao diện (ví dụ: click nút, gửi form).
- Bạn cần thực hiện một logic mà không mong muốn nó được lưu vào bộ nhớ đệm và cần được thực thi lại mỗi khi gọi (ví dụ: gửi dữ liệu lên server).
- Bạn cần truyền tham số vào hàm.
- Hàm của bạn có tác dụng phụ (side effects).
Sử dụng computed khi:
- Bạn cần tính toán một giá trị mới dựa trên dữ liệu hiện có và muốn giá trị đó được cập nhật tự động khi dữ liệu gốc thay đổi.
- Bạn cần tối ưu hóa hiệu suất cho các phép tính phức tạp bằng cách sử dụng bộ nhớ đệm. Nếu giá trị của bạn không thay đổi, nó sẽ không cần tính toán lại.
- Bạn muốn có một giá trị dẫn xuất mà không có tác dụng phụ.
- Bạn muốn truy cập giá trị đó như một thuộc tính, chứ không phải một hàm cần gọi.
5. Lạm dụng methods thay vì computed (và ngược lại)
Một lỗi phổ biến của người mới bắt đầu là sử dụng methods ở nơi mà computed sẽ hiệu quả hơn, và ngược lại.
Lạm dụng methods cho việc tính toán giá trị:
<template>
<div>
<p>Giá trị tổng: {{ calculateTotal() }}</p>
</div>
</template>
<script>
export default {
data() {
return {
price: 10,
quantity: 5
};
},
methods: {
calculateTotal() {
console.log('Calculate total method called!'); // Sẽ gọi lại mỗi khi component re-render
return this.price * this.quantity;
}
}
};
</script>
Trong ví dụ này, calculateTotal() sẽ được gọi lại mỗi khi component re-render (ví dụ: khi dữ liệu khác không liên quan thay đổi, hoặc khi component cha re-render), dẫn đến lãng phí tài nguyên tính toán. Thay vào đó, đây là trường hợp lý tưởng để sử dụng computed:
<template>
<div>
<p>Giá trị tổng: {{ total }}</p>
</div>
</template>
<script>
export default {
data() {
return {
price: 10,
quantity: 5
};
},
computed: {
total() {
console.log('Calculate total computed!'); // Chỉ gọi khi price hoặc quantity thay đổi
return this.price * this.quantity;
}
}
};
</script>
Lạm dụng computed cho các tác dụng phụ hoặc logic có tham số:
<template>
<div>
<button @click="doSomethingComputed">Click me</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello'
};
},
computed: {
// SAI: Computed không nên có tác dụng phụ và không nên là event handler
doSomethingComputed() {
alert(this.message);
return 'Something done';
}
}
};
</script>
Cách dùng này là sai. computed không được thiết kế để xử lý sự kiện hoặc gây ra tác dụng phụ. Thay vào đó, hãy dùng methods:
<template>
<div>
<button @click="doSomethingMethod">Click me</button>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello'
};
},
methods: {
doSomethingMethod() {
alert(this.message);
}
}
};
</script>
Kết luận
methods và computed là hai công cụ mạnh mẽ trong Vue.js, mỗi công cụ có vai trò và ưu điểm riêng. methods phù hợp cho việc xử lý sự kiện và các tác vụ cần thực thi lại mỗi khi được gọi, đặc biệt khi có tác dụng phụ hoặc cần truyền tham số. Ngược lại, computed lý tưởng cho việc tính toán các giá trị dẫn xuất dựa trên dữ liệu hiện có, với lợi thế về hiệu suất nhờ cơ chế bộ nhớ đệm thông minh. Hiểu rõ sự khác biệt giữa chúng và áp dụng đúng cách sẽ giúp bạn viết code Vue sạch hơn, dễ bảo trì hơn và hiệu quả hơn, đảm bảo ứng dụng của bạn hoạt động mượt mà và tối ưu.