Bẫy Memory Leak Trong Web App React/Vue: Bắt Bệnh & Tối Ưu RAM Chuẩn Chuyên Gia
Đăng bởi hong_ngoc •
// Ví dụ sai trong React:
useEffect(() => {
window.addEventListener('resize', handleResize);
setInterval(() => {
fetchData();
}, 1000);
// NGUY HIỂM: Không trả về cleanup function!
}, );
Khi người dùng chuyển trang 10 lần, sẽ có 10 hàm `handleResize` và 10 bộ đếm `setInterval` chạy song song ngầm dưới nền, liên tục kéo bộ nhớ lên theo bội số nhân!
2. Closure Giữ Tham Chiếu Đến Scope Cũ
Closure là tính năng tuyệt vời của JS, nhưng cũng là con dao 2 lưỡi. Khi một hàm con ghi nhớ phạm vi biến của hàm cha, toàn bộ đối tượng trong phạm vi đó sẽ không thể bị giải phóng.
3. Detached DOM Elements (Phần Tử DOM Bi Hủy Nhưng Vẫn Bị Tham Chiếu)
Một phần tử HTML bị xóa khỏi giao diện (DOM Tree), nhưng mã JavaScript của bạn (hoặc một thư viện bên thứ 3) vẫn lưu biến giữ phần tử đó. Kết quả là nguyên cả cây DOM nhỏ (Detached DOM Tree) đó sẽ nằm chình ình trong RAM.
4. Global State & Event Bus Tích Tụ Dữ Liệu Tự Do
Sử dụng Redux, Pinia, Zustand hoặc Event Bus toàn cục để lưu trữ data mà không có cơ chế xóa bỏ (purge) hoặc giới hạn kích thước mảng/bảng băm. Qua thời gian dài lướt app, dung lượng Store tăng trưởng vô hạn.
III. QUY TRÌNH 3 BƯỚC "BẮT BỆNH" MEMORY LEAK BẰNG CHROME DEVTOOLS
Đừng đoán mò! Hãy dùng công cụ có sẵn cực kỳ mạnh mẽ trong Google Chrome để truy tìm điểm rò rỉ bộ nhớ.
Bước 1: Quan Sát Biểu Đồ RAM Thời Gian Thực (Performance Monitor)
• Mở trình duyệt Chrome -> Nhấn
F12
để mở DevTools.
• Nhấn tổ hợp phím
Ctrl + Shift + P
(hoặc Cmd + Shift + P trên Mac), gõ Show Performance Monitor và nhấn Enter.
• Tích chọn chỉ số JS Heap Size và DOM Nodes.
• Thực hiện hành động nghi vấn (Ví dụ: Mở Modal -> Đóng Modal -> Mở lại -> Đóng lại khoảng 10 lần).
• Đánh giá: Nếu đồ thị JS Heap Size và DOM Nodes tăng dần theo bậc thang mà không quay về mức ban đầu sau khi bấm nút Dọn rác (biểu tượng thùng rác ở góc DevTools), chắc chắn app của bạn đang bị Leak!
Bước 2: Chụp & So Sánh Heap Snapshot (Heap Profiling)
• Chuyển sang tab Memory trong DevTools.
• Chọn Heap snapshot -> Bấm Take snapshot (Snapshot 1 - Trạng thái ban đầu).
• Thao tác trên Web (ví dụ: chuyển sang Route khác rồi quay lại).
• Bấm Take snapshot lần 2 (Snapshot 2).
• Ở ô dropdown lựa chọn góc trên, chuyển từ Summary sang Comparison (So sánh) và chọn so sánh với Snapshot 1.
• Sắp xếp theo cột Delta. Những đối tượng nào có chỉ số Delta dương lớn (tăng đột biến về số lượng) chính là thủ phạm!
Bước 3: Truy Tìm Cây Retainers (Ai Đang Giữ Biến Này?)
• Tìm các phần tử có màu đỏ hoặc ghi chú Detached HTMLDivElement... trong danh sách.
• Nhấp vào phần tử đó và nhìn xuống bảng Retainers bên dưới.
• Cây Retainers sẽ chỉ rõ ràng bằng đường dẫn đỏ: Hàm nào, biến nào tại dòng code số bao nhiêu trong file .js đang giữ lại đối tượng này không cho GC xóa.
IV. NGHỆ THUẬT CHỐNG RÒ RỈ BỘ NHỚ: CODE CHUẨN TỪ ĐẦU
Để không phải tốn hàng giờ debug, hãy áp dụng các chuẩn viết code dưới đây:
1. Luôn Viết Cleanup Function Trong React / Vue
Trong React (Functional Component):
useEffect(() => {
const controller = new AbortController(); // Hủy request pending khi unmount
const handleScroll = () => console.log(window.scrollY);
window.addEventListener('scroll', handleScroll);
// Dọn dẹp tuyệt đối khi Component bị unmount
return () => {
window.removeEventListener('scroll', handleScroll);
controller.abort();
};
}, );
Trong Vue 3 (Composition API):
import { onMounted, onUnmounted } from 'vue';
let timer = null;
onMounted(() => {
timer = setInterval(() => { /* do something */ }, 1000);
});
onUnmounted(() => {
clearInterval(timer); // Xóa timer triệt để
});
2. Tận Dụng WeakMap Và WeakSet Cho Dữ Liệu Cấu Trúc Băm
Khi bạn muốn gán meta-data hoặc cache cho một Object mà không muốn cản trở công tác dọn rác của GC, hãy dùng `WeakMap` thay vì `Map` thông thường. Chìa khóa (Key) của `WeakMap` chỉ giữ tham chiếu yếu (Weak reference). Khi Object đó không còn ở đâu dùng nữa, `WeakMap` sẽ tự động giải phóng nó mà không cần bạn delete tay!
// Ví dụ dùng WeakMap làm Cache:
const cacheMap = new WeakMap();
function processUser(userObj) {
if (!cacheMap.has(userObj)) {
const result = heavyComputation(userObj);
cacheMap.set(userObj, result);
}
return cacheMap.get(userObj);
}
// Khi userObj = null, dữ liệu trong cacheMap tự động bị dọn rác!
V. LỜI KẾT & THẢO LUẬN CÙNG CỘNG ĐỒNG https://zolachat.com/
Tối ưu hóa hiệu năng web (Web Performance) không chỉ dừng lại ở việc tối ưu dung lượng file ảnh hay lazy load script. Việc giữ cho dung lượng RAM ổn định và nhẹ nhàng xuyên suốt hành trình trải nghiệm của người dùng chính là yếu tố cốt lõi phân biệt một lập trình viên "biết code" và một lập trình viên "tinh tế".
Hi vọng bài viết chi tiết này sẽ giúp ích cho các anh em trên diễn đàn https://zolachat.com/ trong quá trình xây dựng các sản phẩm Web App mượt mà, tối ưu nhất!
Bây giờ đến lượt anh em:
• Anh em đã từng gặp phải ca Memory Leak "oái ăm" nào trong dự án thực tế chưa?
• Anh em hay dùng công cụ profiling nào ngoài Chrome DevTools để kiểm tra sức khỏe ứng dụng web?
Hãy chia sẻ trải nghiệm hoặc đặt câu hỏi bên dưới bài viết này nhé, mình và các cao thủ lập trình trên https://zolachat.com/ luôn sẵn sàng thảo luận cùng mọi người!Bình luận (7)
Trời ơi, đúng cái mình đang gặp luôn! Cứ tưởng do máy yếu hóa ra là tại chưa dọn dẹp effect kỹ. Cảm ơn chủ thớt nha!
Trước em toàn quên clear interval với event listener, làm web chạy lâu là lag tung chảo. Bài viết cứu cánh em luôn ạ, cảm ơn bác! 👍
Chủ đề này hay nè, mấy cái Memory Leak này khó chịu thật sự. Bác có tip nào dùng Chrome DevTools để check nhanh không ạ?
Đọc xong thấy mình trong đó, bảo sao khách cứ kêu web chậm. Để em về check lại mấy cái component cũ ngay mới được. 😊
Chuẩn bác ơi, không tối ưu là user nó xóa app ngay. Bài viết chi tiết quá, lưu lại để học hỏi dần thôi!
Hồi mới học React em toàn dính lỗi này, mãi sau mới rút được kinh nghiệm. Anh em mới vào nghề nên đọc kỹ bài này nhé! ❤️
Bài viết đúng huyệt quá thớt ơi! Bên mình làm app dashboard realtime dùng React với RxJS cũng hay bị tình trạng leak RAM rải rác mà tìm hoài không ra gốc rễ. Thớt có thể chia sẻ kỹ hơn về mẹo dùng Memory tab trong Chrome DevTools để bắt đúng điểm bị dính uncleaned event listener hay closure được không? Hóng bài viết tiếp theo của thớt dữ lắm!