Vue 3 giới thiệu một kiến trúc mới dựa trên Composition API, mang lại khả năng tổ chức logic linh hoạt hơn, dễ kiểm soát hơn so với Options API truyền thống. Dưới đây là tổng quan kỹ thuật về các thành phần then chốt — từ cách khai báo dữ liệu phản ứng đến quản lý vòng đời và giao tiếp giữa các thành phần.
1. Hàm setup(): Trung tâm của Composition API
Hàm setup() được gọi trước khi khởi tạo instance, thay thế cho beforeCreate và created. Nó nhận hai tham số: props (đối tượng thuộc tính đã khai báo) và context (chứa attrs, slots, emit). Hàm này không được khai báo là async nếu muốn trả về dữ liệu trực tiếp cho template — vì Promise sẽ làm mất khả năng truy cập thuộc tính trong template.
Ví dụ minh họa:
<template>
<div>
<h2>Xin chào {{ fullName }}</h2>
<button @click="updateName">Đổi tên</button>
</div>
</template>
<script>
export default {
name: 'UserProfile',
setup(props, { emit }) {
const firstName = 'Nguyễn';
const lastName = 'Văn A';
const updateName = () => {
// Gửi sự kiện lên cha
emit('name-updated', `${firstName} ${lastName}`);
};
return {
fullName: `${firstName} ${lastName}`,
updateName
};
}
};
</script>
2. ref() và reactive(): Hai cơ chế phản ứng khác nhau
ref(value)tạo một đối tượng phản ứng có thuộc tính.value. Dùng chủ yếu cho kiểu nguyên thủy (string, number, boolean), nhưng cũng hỗ trợ đối tượng — lúc đó nó tự động bao bọc bằngreactive().reactive(obj)tạo mộtProxysâu cho đối tượng hoặc mảng. Không cần.value, nhưng không hỗ trợ kiểu nguyên thủy.
Sự khác biệt rõ ràng khi sử dụng:
import { ref, reactive } from 'vue';
const count = ref(0); // → count.value = 5
const user = reactive({ name: 'Lan' }); // → user.name = 'Hoa'
// Khi cần trích xuất thuộc tính riêng lẻ từ reactive:
const { name } = toRefs(user); // name là ref, có .value
3. Nguyên lý phản ứng: Từ Object.defineProperty sang Proxy
Vue 2 phụ thuộc vào Object.defineProperty để "bắt" thao tác đọc/ghi thuộc tính — dẫn đến hạn chế như không phát hiện thêm/xóa thuộc tính hoặc thay đổi mảng qua chỉ số. Vue 3 sử dụng Proxy để bao bọc toàn bộ đối tượng, kết hợp Reflect để thực thi các thao tác gốc một cách an toàn và đầy đủ:
const source = { id: 1, tags: ['js'] };
const proxy = new Proxy(source, {
get(target, key) {
console.log(`Đang truy cập: ${key}`);
return Reflect.get(target, key);
},
set(target, key, value) {
console.log(`Cập nhật ${key} → ${value}`);
return Reflect.set(target, key, value);
}
});
4. Giám sát và tính toán: watch, computed, watchEffect
computed(() => ...)tạo giá trị suy dẫn, tự động tái tính khi phụ thuộc thay đổi.watch(source, callback, options)phản ứng theo thay đổi cụ thể — hỗ trợ giám sát sâu (deep: true) hoặc từng thuộc tính riêng lẻ (dùng hàm getter).watchEffect(callback)tự động thu thập phụ thuộc trong lần chạy đầu tiên — không cần chỉ định nguồn rõ ràng, rất tiện cho side-effect.
Ví dụ giám sát thuộc tính lồng sâu:
const profile = reactive({
contact: { email: 'a@example.com', phone: '090...' }
});
// Chỉ giám sát email, không kích hoạt deep thừa
watch(() => profile.contact.email, (newVal) => {
console.log('Email đã thay đổi:', newVal);
});
5. Chu kỳ sống: API dạng hàm và tương thích ngược
Các hook cũ như beforeDestroy được đổi tên thành beforeUnmount. Vue 3 cung cấp các hàm hook tương ứng dưới dạng Composition API: onMounted, onUpdated, onBeforeUnmount, v.v. Chúng được gọi ngay trong setup() và ưu tiên thực thi trước các hook dạng option.
6. Truyền dữ liệu tổ tiên – hậu duệ: provide / inject
Một giải pháp nhẹ để chia sẻ trạng thái giữa các thành phần không trực tiếp liên quan:
// Thành phần cha (hoặc tổ tiên)
setup() {
const theme = reactive({ color: '#333', fontSize: '16px' });
provide('appTheme', theme);
return { theme };
}
// Thành phần con (ở bất kỳ cấp độ nào)
setup() {
const theme = inject('appTheme');
return { theme };
}
7. Các hàm tiện ích nâng cao
toRef()vàtoRefs(): Tạoreftrỏ tới thuộc tính của đối tượngreactive, giúp giữ tính phản ứng khi phân rã.shallowRef()/shallowReactive(): Chỉ phản ứng ở mức độ nông — hữu ích khi xử lý cấu trúc dữ liệu lớn nhưng thay đổi chủ yếu ở tầng trên.readonly()/shallowReadonly(): Đảm bảo dữ liệu không bị sửa từ bên ngoài — đặc biệt phù hợp khi truyền xuống thành phần con.customRef(): Cho phép kiểm soát chi tiết việc theo dõi và kích hoạt — ví dụ: triển khai debounce cho input tìm kiếm.
8. Các tính năng cấu trúc mới
- Fragment: Cho phép template không cần thẻ bao ngoài duy nhất — nhiều nút cùng cấp được render trực tiếp.
- Teleport: Di chuyển DOM ra khỏi vị trí ban đầu (ví dụ: modal vào
<body>), tránh vấn đề overflow hoặc z-index. - Suspense: Xử lý tải bất đồng bộ với fallback UI — dùng kèm
defineAsyncComponent()để cải thiện trải nghiệm người dùng.