[Medium] watch vs watchEffect
1. What are watch and watchEffect?
watch và watchEffect là gì?
watch và watchEffect là hai API trong Vue 3 Composition API dùng để theo dõi thay đổi dữ liệu reactive.
watch
Định nghĩa: chỉ định rõ ràng nguồn dữ liệu cần theo dõi, thực thi hàm callback khi dữ liệu thay đổi.
<script setup>
import { ref, watch } from 'vue';
const count = ref(0);
const message = ref('Hello');
// Theo dõi một nguồn dữ liệu
watch(count, (newValue, oldValue) => {
console.log(`count thay đổi từ ${oldValue} thành ${newValue}`);
});
// Theo dõi nhiều nguồn dữ liệu
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log('count hoặc message đã thay đổi');
});
</script>
watchEffect
Định nghĩa: tự động theo dõi dữ liệu reactive được sử dụng trong hàm callback, tự động thực thi khi các dữ liệu đó thay đổi.
<script setup>
import { ref, watchEffect } from 'vue';
const count = ref(0);
const message = ref('Hello');
// Tự động theo dõi count và message
watchEffect(() => {
console.log(`count: ${count.value}, message: ${message.value}`);
// Tự động thực thi khi count hoặc message thay đổi
});
</script>
2. watch vs watchEffect: Key Differences
Sự khác biệt chính giữa watch và watchEffect
1. Chỉ định nguồn dữ liệu
watch: chỉ định rõ ràng dữ liệu cần theo dõi
const count = ref(0);
const message = ref('Hello');
// Chỉ định rõ ràng theo dõi count
watch(count, (newVal, oldVal) => {
console.log('count đã thay đổi');
});
// Chỉ định rõ ràng theo dõi nhiều dữ liệu
watch([count, message], ([newCount, newMessage]) => {
console.log('count hoặc message đã thay đổi');
});
watchEffect: tự động theo dõi dữ liệu được sử dụng
const count = ref(0);
const message = ref('Hello');
// Tự động theo dõi count và message (vì được sử dụng trong callback)
watchEffect(() => {
console.log(count.value); // Tự động theo dõi count
console.log(message.value); // Tự động theo dõi message
});
2. Thời điểm thực thi
watch: thực thi lười (lazy) theo mặc định, chỉ thực thi khi dữ liệu thay đổi
const count = ref(0);
watch(count, (newVal) => {
console.log('Thực thi'); // Chỉ thực thi khi count thay đổi
});
count.value = 1; // Kích hoạt thực thi
watchEffect: thực thi ngay lập tức, sau đó theo dõi thay đổi
const count = ref(0);
watchEffect(() => {
console.log('Thực thi'); // Thực thi ngay lập tức một lần
console.log(count.value);
});
count.value = 1; // Thực thi lần nữa
3. Truy cập giá trị cũ
watch: có thể truy cập giá trị cũ
const count = ref(0);
watch(count, (newVal, oldVal) => {
console.log(`Từ ${oldVal} thành ${newVal}`);
});
watchEffect: không thể truy cập giá trị cũ
const count = ref(0);
watchEffect(() => {
console.log(count.value); // Chỉ truy cập được giá trị hiện tại
// Không thể lấy giá trị cũ
});
4. Dừng theo dõi
watch: trả về hàm dừng
const count = ref(0);
const stopWatch = watch(count, (newVal) => {
console.log(newVal);
});
// Dừng theo dõi
stopWatch();
watchEffect: trả về hàm dừng
const count = ref(0);
const stopEffect = watchEffect(() => {
console.log(count.value);
});
// Dừng theo dõi
stopEffect();
3. When to Use watch vs watchEffect?
Khi nào dùng watch? Khi nào dùng watchEffect?
Trường hợp dùng watch
-
Cần chỉ định rõ ràng dữ liệu theo dõi
watch(userId, (newId) => {fetchUser(newId);}); -
Cần truy cập giá trị cũ
watch(count, (newVal, oldVal) => {console.log(`Từ ${oldVal} thành ${newVal}`);}); -
Cần thực thi lười (chỉ khi thay đổi)
watch(searchQuery, (newQuery) => {if (newQuery.length > 2) {search(newQuery);}}); -
Cần kiểm soát chi tiết hơn
watch(() => user.value.id,(newId) => {fetchUser(newId);},{ immediate: true, deep: true });
Trường hợp dùng watchEffect
-
Tự động theo dõi nhiều dữ liệu liên quan
watchEffect(() => {// Tự động theo dõi tất cả dữ liệu reactive được sử dụngif (user.value && permissions.value.includes('admin')) {loadAdminData();}}); -
Không cần giá trị cũ
watchEffect(() => {console.log(`Bộ đếm hiện tại: ${count.value}`);}); -
Cần thực thi ngay lập tức
watchEffect(() => {// Thực thi ngay, sau đó theo dõi thay đổiupdateChart(count.value, message.value);});
4. Common Interview Questions
Câu hỏi phỏng vấn thường gặp
Câu hỏi 1: Khác biệt cơ bản
Giải thích thứ tự thực thi và kết quả đầu ra của đoạn code sau.
const count = ref(0);
const message = ref('Hello');
// watch
watch(count, (newVal) => {
console.log('watch:', newVal);
});
// watchEffect
watchEffect(() => {
console.log('watchEffect:', count.value, message.value);
});
count.value = 1;
message.value = 'World';
Nhấp để xem đáp án
const count = ref(0);
const message = ref('Hello');
// watch: thực thi lười, không thực thi ngay
watch(count, (newVal) => {
console.log('watch:', newVal);
});
// watchEffect: thực thi ngay lập tức
watchEffect(() => {
console.log('watchEffect:', count.value, message.value);
// In ngay: watchEffect: 0 Hello
});
count.value = 1;
// Kích hoạt watch: watch: 1
// Kích hoạt watchEffect: watchEffect: 1 Hello
message.value = 'World';
// watch không theo dõi message, không thực thi
// watchEffect theo dõi message, thực thi: watchEffect: 1 World
Thứ tự đầu ra:
watchEffect: 0 Hello(thực thi ngay)watch: 1(count thay đổi)watchEffect: 1 Hello(count thay đổi)watchEffect: 1 World(message thay đổi)
Khác biệt chính:
watchthực thi lười, chỉ thực thi khi dữ liệu theo dõi thay đổiwatchEffectthực thi ngay, sau đó theo dõi tất cả dữ liệu được sử dụng
Câu hỏi 2: Truy cập giá trị cũ
Giải thích cách lấy giá trị cũ khi dùng watchEffect.
Nhấp để xem đáp án
Vấn đề: watchEffect không thể truy cập trực tiếp giá trị cũ
const count = ref(0);
watchEffect(() => {
console.log(count.value); // Chỉ truy cập được giá trị hiện tại
// Không thể lấy giá trị cũ
});
Giải pháp 1: Dùng ref lưu giá trị cũ
const count = ref(0);
const prevCount = ref(0);
watchEffect(() => {
console.log(`Từ ${prevCount.value} thành ${count.value}`);
prevCount.value = count.value; // Cập nhật giá trị cũ
});
Giải pháp 2: Dùng watch (nếu cần giá trị cũ)
const count = ref(0);
watch(count, (newVal, oldVal) => {
console.log(`Từ ${oldVal} thành ${newVal}`);
});
Khuyến nghị:
- Nếu cần giá trị cũ, ưu tiên dùng
watch watchEffectphù hợp với tình huống không cần giá trị cũ
Câu hỏi 3: Chọn watch hay watchEffect?
Cho biết các tình huống sau nên dùng watch hay watchEffect.
// Tình huống 1: Theo dõi ID người dùng thay đổi, tải lại dữ liệu
const userId = ref(1);
// ?
// Tình huống 2: Tự động bật nút gửi khi form hợp lệ
const form = reactive({ username: '', password: '' });
const isValid = computed(() => form.username && form.password);
// ?
// Tình huống 3: Theo dõi từ khóa tìm kiếm, thực hiện tìm kiếm (cần debounce)
const searchQuery = ref('');
// ?
Nhấp để xem đáp án
Tình huống 1: Theo dõi ID người dùng
const userId = ref(1);
// Dùng watch: chỉ định rõ ràng dữ liệu theo dõi
watch(userId, (newId) => {
fetchUser(newId);
});
Tình huống 2: Xác thực form
const form = reactive({ username: '', password: '' });
const isValid = computed(() => form.username && form.password);
// Dùng watchEffect: tự động theo dõi dữ liệu liên quan
watchEffect(() => {
submitButton.disabled = !isValid.value;
});
Tình huống 3: Tìm kiếm (cần debounce)
const searchQuery = ref('');
// Dùng watch: cần kiểm soát chi tiết hơn (debounce)
let timeoutId;
watch(searchQuery, (newQuery) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
search(newQuery);
}, 300);
});
Nguyên tắc chọn:
- Chỉ định rõ ràng dữ liệu theo dõi ->
watch - Tự động theo dõi nhiều dữ liệu liên quan ->
watchEffect - Cần giá trị cũ hoặc kiểm soát chi tiết ->
watch - Cần thực thi ngay lập tức ->
watchEffect
5. Best Practices
Thực hành tốt nhất
Cách làm khuyến nghị
// 1. Dùng watch khi chỉ định rõ ràng dữ liệu theo dõi
watch(userId, (newId) => {
fetchUser(newId);
});
// 2. Dùng watchEffect khi tự động theo dõi nhiều dữ liệu liên quan
watchEffect(() => {
if (user.value && permissions.value.includes('admin')) {
loadAdminData();
}
});
// 3. Dùng watch khi cần giá trị cũ
watch(count, (newVal, oldVal) => {
console.log(`Từ ${oldVal} thành ${newVal}`);
});
// 4. Nhớ dọn dẹp watcher
onUnmounted(() => {
stopWatch();
stopEffect();
});
Cách làm cần tránh
// 1. Không thực hiện thao tác bất đồng bộ trong watchEffect mà không xử lý dọn dẹp
watchEffect(async () => {
const data = await fetchData(); // Có thể gây rò rỉ bộ nhớ
// ...
});
// 2. Không lạm dụng watchEffect
// Nếu chỉ cần theo dõi dữ liệu cụ thể, watch rõ ràng hơn
watchEffect(() => {
console.log(count.value); // Nếu chỉ cần theo dõi count, watch phù hợp hơn
});
// 3. Không sửa đổi dữ liệu theo dõi trong watchEffect (có thể gây vòng lặp vô hạn)
watchEffect(() => {
count.value++; // Có thể gây vòng lặp vô hạn
});
6. Interview Summary
Tổng kết phỏng vấn
Ghi nhớ nhanh
watch:
- Chỉ định rõ ràng dữ liệu theo dõi
- Thực thi lười (mặc định)
- Có thể truy cập giá trị cũ
- Phù hợp tình huống cần kiểm soát chi tiết
watchEffect:
- Tự động theo dõi dữ liệu được sử dụng
- Thực thi ngay lập tức
- Không thể truy cập giá trị cũ
- Phù hợp tự động theo dõi nhiều dữ liệu liên quan
Nguyên tắc chọn:
- Chỉ định rõ ràng theo dõi ->
watch - Tự động theo dõi ->
watchEffect - Cần giá trị cũ ->
watch - Cần thực thi ngay ->
watchEffect
Ví dụ trả lời phỏng vấn
Q: Sự khác biệt giữa watch và watchEffect là gì?
"watch và watchEffect đều là API của Vue 3 dùng để theo dõi thay đổi dữ liệu reactive. Các khác biệt chính bao gồm: 1) Nguồn dữ liệu: watch cần chỉ định rõ ràng dữ liệu theo dõi, watchEffect tự động theo dõi dữ liệu reactive được sử dụng trong callback; 2) Thời điểm thực thi: watch thực thi lười theo mặc định, chỉ thực thi khi dữ liệu thay đổi, watchEffect thực thi ngay rồi theo dõi thay đổi; 3) Truy cập giá trị cũ: watch có thể truy cập giá trị cũ, watchEffect thì không; 4) Trường hợp sử dụng: watch phù hợp khi cần chỉ định dữ liệu theo dõi hoặc cần giá trị cũ, watchEffect phù hợp khi tự động theo dõi nhiều dữ liệu liên quan."
Q: Khi nào nên dùng watch? Khi nào dùng watchEffect?
"Dùng watch khi: 1) cần chỉ định rõ ràng dữ liệu theo dõi; 2) cần truy cập giá trị cũ; 3) cần thực thi lười, chỉ khi thay đổi; 4) cần kiểm soát chi tiết hơn (tùy chọn immediate, deep). Dùng watchEffect khi: 1) tự động theo dõi nhiều dữ liệu liên quan; 2) không cần giá trị cũ; 3) cần thực thi ngay lập tức. Nói chung, nếu chỉ cần theo dõi dữ liệu cụ thể, watch rõ ràng hơn; nếu cần tự động theo dõi nhiều dữ liệu liên quan, watchEffect tiện hơn."