useEffect vs useMemo: 5 Khác Biệt Quan Trọng Bạn Cần Hiểu Rõ
Nhầm lẫn giữa useEffect và useMemo là một trong những nguồn gốc phổ biến nhất gây ra các vấn đề hiệu suất trong ứng dụng React. Để dễ hình dung: hãy coi useMemo như một chiếc máy tính bỏ túi biết ghi nhớ kết quả, còn useEffect như một nhân viên trực tổng đài — chờ tín hiệu từ bên ngoài rồi mới hành động.

Trung Vũ Hoàng
Tác giả
1. Bản Chất Cốt Lõi Của Từng Hook
useMemo được sinh ra để giải quyết bài toán Memoization — lưu giữ kết quả của một phép tính tốn kém và chỉ tính toán lại khi các dependency thực sự thay đổi. Mục tiêu duy nhất của nó là ngăn các phép tính nặng chạy lại vô ích sau mỗi lần Component re-render.
useEffect thì ngược lại — đây là nơi xử lý Side Effects: những tác vụ nằm ngoài phạm vi hiển thị của Component như gọi API, thiết lập subscription, hay tương tác trực tiếp với DOM. Nếu bạn nhét logic tính toán vào useEffect, React buộc phải render thêm một lần nữa để cập nhật trạng thái — lãng phí hoàn toàn.
2. Thời Điểm Thực Thi: Khác Biệt Sống Còn
useMemo chạy ngay trong quá trình render (Rendering phase). Khi React đọc code Component để chuẩn bị xuất UI, nó kiểm tra dependency array của useMemo — nếu không thay đổi, giá trị cũ được trả về ngay lập tức. Vì vậy, tuyệt đối không được đưa side effect vào bên trong useMemo, điều đó sẽ làm chậm quá trình hiển thị.
useEffect chỉ chạy sau khi render hoàn tất và trình duyệt đã vẽ xong UI (Commit phase). Thiết kế này đảm bảo các tác vụ nặng như fetch data không block người dùng nhìn thấy giao diện — đây là chủ tâm của React để giữ ứng dụng luôn phản hồi nhanh.
3. Khi Nào Nên Dùng useMemo?
Chỉ nên dùng useMemo trong hai tình huống rõ ràng:
Phép tính nặng: Lọc danh sách hàng ngàn phần tử, chạy thuật toán tìm kiếm phức tạp, xử lý dữ liệu lớn...
Đảm bảo tính nhất quán tham chiếu: Khi bạn trả về Object hoặc Array và truyền xuống Component con wrapped bởi
React.memo. Nếu không có useMemo, Object mới được tạo sau mỗi render — Component con coi đó là "dữ liệu mới" và re-render vô ích.
Đừng dùng useMemo cho phép tính đơn giản — chi phí khởi tạo hook và so sánh dependency có thể còn lớn hơn lợi ích mang lại.
4. Ứng Dụng Thực Tế Của useEffect
useEffect là trạm điều khiển cho mọi thứ diễn ra bên ngoài Component. Các tình huống phổ biến nhất:
Gọi API khi Component mount hoặc khi dependency thay đổi.
Đăng ký (subscribe) WebSocket để nhận dữ liệu thời gian thực.
Thiết lập và dọn dẹp Event Listener.
Một điểm mà useMemo hoàn toàn không có: hàm cleanup. Khi Component unmount hoặc trước khi effect chạy lại, hàm cleanup cho phép bạn hủy yêu cầu API đang dở, xóa listener, giải phóng bộ nhớ — tránh memory leak.
Mẹo nhỏ: Trước khi dùng useEffect để cập nhật state, hãy tự hỏi: "Có thể tính toán giá trị này trực tiếp trong render không?" Nếu có — hãy dùng useMemo hoặc tính thẳng trong thân hàm. Không cần useEffect.
5. Tổng Hợp 5 Khác Biệt Then Chốt
Khác biệt 1: Mục đích
useMemo→ Ghi nhớ giá trị ("nhớ cái gì đó").useEffect→ Xử lý side effect ("làm gì đó").
Khác biệt 2: Thời điểm chạy
useMemo→ Trong quá trình render (Before Paint).useEffect→ Sau khi render hoàn tất (After Paint).
Khác biệt 3: Giá trị trả về
useMemo→ Một giá trị cụ thể (số, chuỗi, mảng, object...).useEffect→ undefined hoặc một hàm cleanup.
Khác biệt 4: Hỗ trợ cleanup
useMemo→ Không có.useEffect→ Có hàm cleanup khi unmount hoặc dependency thay đổi.
Khác biệt 5: Ảnh hưởng đến render
useMemo→ Tránh render thừa bằng cách giữ nguyên giá trị.useEffect→ Có thể kích hoạt thêm render nếu cập nhật state bên trong.
6. Những Sai Lầm Cần Tránh
Stale Closures: Dùng biến bên trong hook nhưng quên khai báo trong dependency array. Code đọc dữ liệu cũ mà không có bất kỳ cảnh báo nào — loại bug âm thầm khó tìm nhất.
Xử lý event trong useEffect: Khi người dùng click "Mua hàng", xử lý logic ngay trong onClick — đừng thay đổi state trung gian rồi chờ useEffect "bắt" lấy. Cách trực tiếp luôn rõ ràng và dễ debug hơn.
Một useEffect làm quá nhiều việc: Tuân thủ nguyên tắc Single Responsibility — mỗi useEffect chỉ đảm nhận một nhiệm vụ. Điều này giúp React quản lý re-render chính xác hơn và giúp đồng đội đọc code dễ hơn.
Tổng Kết
Điều tốt nhất bạn có thể làm là giữ Component đơn giản nhất có thể. Viết code đúng chức năng trước — rồi mới đo lường bằng công cụ profiler để quyết định có cần Memoize hay không. Đừng tối ưu hóa vội vàng dựa trên cảm tính.
Câu hỏi thường gặp
Bài viết liên quan

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.

Promise Nâng Cao: all, allSettled, race, any — Khi Nào Dùng Cái Nào?
Nếu bạn chỉ dùng async/await tuần tự, ứng dụng của bạn đang bỏ phí tiềm năng hiệu suất — mỗi request phải chờ request trước xong mới bắt đầu. Các phương thức tĩnh của Promise (all, allSettled, race, any) cho phép bạn điều phối nhiều tác vụ bất đồng bộ song song theo chiến lược phù hợp từng bài toán.

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.