Vue 3: computed, watch, props và emit để các component giao tiếp

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Sau khi biết ref và template, bước tiếp theo trong Vue là xây dựng nhiều component và cho chúng nói chuyện với nhau. Bài này giới thiệu bốn công cụ cốt lõi: computed cho dữ liệu dẫn xuất, watch cho phản ứng với thay đổi, và props cùng emit cho giao tiếp cha con.

computed: giá trị tính từ dữ liệu khác

Khi một giá trị được suy ra từ dữ liệu khác, hãy dùng computed thay vì lưu thêm state:

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

const tuKhoa = ref('');
const ds = ref(['HTML', 'CSS', 'JavaScript', 'Vue', 'React']);

const daLoc = computed(() =>
  ds.value.filter(x => x.toLowerCase().includes(tuKhoa.value.toLowerCase()))
);
</script>

<template>
  <input v-model='tuKhoa' placeholder='Tìm...' />
  <p>Tìm thấy {{ daLoc.length }} kết quả</p>
  <ul><li v-for='x in daLoc' :key='x'>{{ x }}</li></ul>
</template>

Computed tự biết nó phụ thuộc vào những ref nào, chỉ tính lại khi chúng đổi và lưu kết quả đệm. Quy tắc quan trọng: hàm computed phải thuần, tức chỉ tính ra giá trị, không sửa dữ liệu hay gọi API bên trong.

watch: làm việc khi dữ liệu thay đổi

Khi cần thực hiện hành động (side effect) như gọi API hay lưu trữ mỗi khi một giá trị đổi, dùng watch:

import { ref, watch } from 'vue';

const theme = ref(localStorage.getItem('theme') || 'light');

watch(theme, (moi, cu) => {
  localStorage.setItem('theme', moi);
  console.log('Đổi từ', cu, 'sang', moi);
});

// theo dõi thuộc tính của object phản ứng
watch(() => form.email, (moi) => { /* ... */ });

Để chạy ngay lần đầu, thêm tùy chọn { immediate: true }. Còn watchEffect tự động theo dõi mọi ref được dùng bên trong, gọn nhưng khó kiểm soát hơn. Quy tắc chọn: tính ra giá trị thì dùng computed, làm một việc phản ứng với thay đổi thì dùng watch.

Props: dữ liệu từ cha xuống con

Component con khai báo những gì nó nhận bằng defineProps:

<!-- TheKhoaHoc.vue -->
<script setup>
const props = defineProps({
  ten: { type: String, required: true },
  gia: { type: Number, default: 0 },
});
</script>

<template>
  <article>
    <h3>{{ ten }}</h3>
    <p>{{ gia === 0 ? 'Miễn phí' : gia.toLocaleString('vi-VN') + ' đ' }}</p>
  </article>
</template>

Component cha dùng nó như thẻ HTML:

<TheKhoaHoc ten='Vue cơ bản' :gia='250000' />

Chú ý dấu hai chấm khi truyền số: không có nó, gia='250000' là chuỗi. Props chảy một chiều xuống và con không được sửa trực tiếp props. Nếu cần thay đổi, hãy báo cho cha.

Emit: con báo lên cha

<!-- NutThich.vue -->
<script setup>
const emit = defineEmits(['thich']);
</script>
<template>
  <button @click="emit('thich', 1)">Thích</button>
</template>
<!-- cha -->
<NutThich @thich='soThich += $event' />

Con phát ra sự kiện kèm dữ liệu (emit), cha lắng nghe bằng @ten-su-kien. Đây là mô hình "props xuống, events lên" đảm bảo luồng dữ liệu dễ theo dõi.

v-model trên component

Từ Vue 3.4, macro defineModel giúp tạo component hỗ trợ v-model rất gọn:

<!-- OTimKiem.vue -->
<script setup>
const giaTri = defineModel();
</script>
<template>
  <input v-model='giaTri' />
</template>

<!-- cha -->
<OTimKiem v-model='tuKhoa' />

Nếu dự án của bạn dùng phiên bản cũ hơn, hãy dùng cặp prop modelValue và sự kiện update:modelValue để đạt cùng kết quả.

Slot: chèn nội dung từ ngoài

Tương tự children trong React, slot cho phép cha truyền nội dung vào chỗ định sẵn của con: con đặt <slot />, cha viết nội dung bên trong thẻ của con. Slot có tên (<slot name='footer' />) hỗ trợ nhiều vùng nội dung.

Sai lầm thường gặp

  1. Sửa trực tiếp prop trong component con. Hãy dùng emit hoặc sao chép sang state cục bộ.
  2. Dùng watch để tính giá trị dẫn xuất, nơi computed phù hợp hơn.
  3. Quên .value trong script, hoặc ngược lại thêm .value trong template.
  4. Thiếu :key hoặc dùng chỉ số mảng làm key khi danh sách thay đổi thứ tự.

Ví dụ ghép tất cả: danh sách khóa học có lọc

Cha giữ dữ liệu và từ khóa, con chỉ hiển thị và báo sự kiện:

<!-- cha: App.vue -->
<script setup>
import { ref, computed } from 'vue';
import TheKhoaHoc from './TheKhoaHoc.vue';

const tuKhoa = ref('');
const khoa = ref([
  { id: 1, ten: 'HTML cơ bản', gia: 0 },
  { id: 2, ten: 'Vue 3 thực chiến', gia: 300000 },
]);
const hienThi = computed(() =>
  khoa.value.filter(k => k.ten.toLowerCase().includes(tuKhoa.value.toLowerCase()))
);
</script>

<template>
  <input v-model='tuKhoa' placeholder='Tìm khóa học' />
  <TheKhoaHoc v-for='k in hienThi' :key='k.id' :ten='k.ten' :gia='k.gia' />
</template>

Dữ liệu dẫn xuất nằm trong computed, component con thuần hiển thị. Cấu trúc này dễ đọc, dễ kiểm thử và dễ mở rộng khi thêm tính năng.

Kết luận

Computed cho giá trị dẫn xuất, watch cho phản ứng có tác dụng phụ, props đưa dữ liệu xuống, emit đưa sự kiện lên. Nắm vững bốn thứ này là bạn đủ sức xây dựng các ứng dụng Vue nhiều thành phần.

Bạn có thể luyện tập thêm với các dự án nhỏ trong khóa học tại codebem.com để quen tay hơn.

author_photo
Nguyễn Đình Cường

0 comment