Giới thiệu: Khai thác sức mạnh "nông" của Reactivity trong Vue
Chào các bạn, những người đồng hành cùng tôi trên hành trình khám phá Vue.js! Chúng ta đều biết reactivity (tính phản ứng) là một trong những "phép màu" của Vue, giúp dữ liệu thay đổi là giao diện cập nhật ngay lập tức. Thông thường, chúng ta sử dụng ref() cho giá trị đơn lẻ và reactive() cho các đối tượng phức tạp, và Vue sẽ "phù phép" để theo dõi mọi ngóc ngách của dữ liệu.
Nhưng đôi khi, sự "sâu sắc" này lại không phải lúc nào cũng là điều tốt. Với các cấu trúc dữ liệu khổng lồ hoặc khi tích hợp với thư viện bên ngoài, việc Vue cố gắng theo dõi mọi thứ có thể trở thành gánh nặng hiệu suất. Đó là lúc chúng ta cần đến những người anh em "nông" hơn: shallowRef và shallowReactive.
Vậy, khi nào "nông" lại là "sâu"? Hãy cùng tìm hiểu!
ShallowRef: Chỉ quan tâm đến lớp vỏ ngoài
Giống như tên gọi, shallowRef tạo ra một ref "nông". Điều này có nghĩa là gì?
- Nó chỉ theo dõi sự thay đổi của chính thuộc tính
.valuecủa ref đó. - Nó KHÔNG theo dõi các thay đổi bên trong đối tượng hoặc mảng mà
.valueđang giữ.
Khi nào nên dùng ShallowRef?
- Tối ưu hiệu suất với đối tượng lớn, bất biến: Khi bạn có một đối tượng dữ liệu khổng lồ và bạn biết rằng mình sẽ chỉ thay thế toàn bộ đối tượng đó (thay vì sửa từng thuộc tính con),
shallowRefsẽ hiệu quả hơn nhiều. Vue không cần tạo proxy cho tất cả các thuộc tính lồng sâu, tiết kiệm bộ nhớ và CPU. - Tích hợp với thư viện bên ngoài: Nếu bạn đang làm việc với một đối tượng được quản lý bởi một thư viện JavaScript khác (ví dụ: một đối tượng scene của Three.js, một biểu đồ D3.js), bạn có thể muốn Vue chỉ theo dõi khi bạn gán một đối tượng mới hoàn toàn, tránh xung đột hoặc proxying không cần thiết.
Ví dụ minh họa ShallowRef
Hãy xem ví dụ sau:
<template> <p>User Name: {{ user.name }}</p> <p>User Age: {{ user.age }}</p> <button @click="changeUserName">Change User Name (Doesn't update)</button> <button @click="replaceUser">Replace User Object (Updates)</button></template><script setup>import { shallowRef, triggerRef } from 'vue';const user = shallowRef({ name: 'Alice', age: 30 });const changeUserName = () => { user.value.name = 'Bob'; // Thay đổi thuộc tính bên trong, KHÔNG kích hoạt cập nhật giao diện console.log(user.value); // Nếu bạn muốn ép buộc cập nhật, bạn có thể dùng triggerRef() // triggerRef(user);};const replaceUser = () => { user.value = { name: 'Charlie', age: 25 }; // Thay thế toàn bộ đối tượng, KÍCH HOẠT cập nhật giao diện console.log(user.value);};</script>Trong ví dụ trên, khi bạn bấm "Change User Name", giao diện sẽ không cập nhật vì user.value không bị thay thế. Nhưng khi bạn bấm "Replace User Object", giao diện sẽ cập nhật vì bạn đã gán một đối tượng mới cho user.value.
ShallowReactive: Sức mạnh ở các thuộc tính cấp 1
Tương tự như shallowRef, shallowReactive cũng tạo ra một đối tượng reactive "nông".
- Nó chỉ theo dõi các thay đổi của các thuộc tính cấp cao nhất của đối tượng.
- Nó KHÔNG theo dõi các thay đổi của các thuộc tính lồng sâu bên trong các đối tượng con.
Khi nào nên dùng ShallowReactive?
- Tối ưu hiệu suất cho đối tượng lớn với các thuộc tính cấp 1 thường xuyên thay đổi: Nếu bạn có một đối tượng phức tạp nhưng bạn chỉ quan tâm đến việc thay đổi các thuộc tính trực tiếp của nó (ví dụ: gán một đối tượng con mới cho một thuộc tính cấp 1),
shallowReactivesẽ là lựa chọn phù hợp. - Tránh proxying sâu không cần thiết: Giúp tiết kiệm tài nguyên khi không cần thiết phải theo dõi mọi cấp độ lồng ghép của dữ liệu.
Ví dụ minh họa ShallowReactive
Hãy xem ví dụ sau:
<template> <p>User Info: {{ userInfo.name }} - {{ userInfo.details.city }}</p> <button @click="changeCity">Change City (Doesn't update)</button> <button @click="changeName">Change Name (Updates)</button> <button @click="replaceDetails">Replace Details (Updates)</button></template><script setup>import { shallowReactive } from 'vue';const userInfo = shallowReactive({ name: 'David', age: 40, details: { city: 'Hanoi', country: 'Vietnam' }});const changeCity = () => { userInfo.details.city = 'Ho Chi Minh'; // Thay đổi thuộc tính lồng sâu, KHÔNG kích hoạt cập nhật giao diện console.log(userInfo.details.city);};const changeName = () => { userInfo.name = 'Eve'; // Thay đổi thuộc tính cấp 1, KÍCH HOẠT cập nhật giao diện console.log(userInfo.name);};const replaceDetails = () => { userInfo.details = { city: 'London', country: 'UK' }; // Thay thế thuộc tính cấp 1 (details), KÍCH HOẠT cập nhật giao diện console.log(userInfo.details);};</script>Ở đây, thay đổi userInfo.details.city sẽ không làm giao diện cập nhật vì nó là một thuộc tính lồng sâu. Tuy nhiên, thay đổi userInfo.name hoặc gán một đối tượng mới cho userInfo.details (một thuộc tính cấp 1) sẽ kích hoạt cập nhật.
Kết luận: Khi nào "nông" là lựa chọn tối ưu?
Mặc định, bạn nên luôn sử dụng ref và reactive bởi chúng cung cấp reactivity đầy đủ và an toàn nhất. Tuy nhiên, shallowRef và shallowReactive là những công cụ mạnh mẽ khi bạn cần kiểm soát chặt chẽ hơn cơ chế reactivity của Vue để:
- Tối ưu hiệu suất: Với các cấu trúc dữ liệu lớn, phức tạp mà bạn thường xuyên thay thế toàn bộ hoặc chỉ chỉnh sửa các thuộc tính cấp cao nhất.
- Tích hợp với thư viện ngoài: Khi bạn muốn Vue chỉ theo dõi ở một mức độ nhất định, để tránh xung đột hoặc proxying không cần thiết với các đối tượng được quản lý bởi thư viện bên thứ ba.
- Giảm gánh nặng bộ nhớ và CPU: Bằng cách tránh tạo các proxy sâu không cần thiết.
Hãy nhớ, việc lựa chọn "nông" hay "sâu" phụ thuộc vào yêu cầu cụ thể của ứng dụng và cấu trúc dữ liệu của bạn. Hiểu rõ từng công cụ sẽ giúp bạn xây dựng những ứng dụng Vue không chỉ mạnh mẽ mà còn mượt mà và hiệu quả!