useMemo vs useCallback: 5 Sai Lầm Khiến React App Của Bạn Chậm Đi

Hiểu đúng bản chất của Memoization là nền tảng để bạn tránh những lỗi sai cơ bản và xây dựng codebase sạch, dễ bảo trì hơn. Bài viết này phân tích kỹ lưỡng cách hai hook hoạt động, điểm khác nhau giữa chúng và — quan trọng hơn cả — những tình huống bạn tuyệt đối không nên dùng chúng.

useMemouseCallbacktối ưu Reactmemoizationhook
Ảnh bìa bài viết: useMemo vs useCallback: 5 Sai Lầm Khiến React App Của Bạn Chậm Đi
Ảnh đại diện của Trung Vũ Hoàng

Trung Vũ Hoàng

Tác giả

22/3/20265 phút đọc

1. Memoization Trong React Là Gì?

Một điều cần hiểu rõ trước tiên: mọi thao tác trong React đều có chi phí nhất định. Mỗi lần Component re-render, toàn bộ biến và hàm bên trong nó đều được khởi tạo lại từ đầu.

Memoization là kỹ thuật lưu lại kết quả tính toán hoặc định nghĩa hàm vào bộ nhớ đệm. Khi các giá trị đầu vào (dependencies) không đổi, React trả về dữ liệu đã cache thay vì tính toán lại — tiết kiệm tài nguyên CPU.

Tuy nhiên, việc lưu cache đòi hỏi bộ nhớ và thời gian để so sánh dependencies qua từng lần render. Vì vậy, tối ưu hóa quá sớm và thiếu suy nghĩ thường gây hại nhiều hơn lợi.

2. useMemo: Ghi Nhớ Kết Quả Tính Toán

useMemo là hook giúp bạn cache lại giá trị trả về của một hàm — đặc biệt hữu ích khi hàm đó thực hiện các phép tính tốn kém tài nguyên.

Cơ Chế Hoạt Động

Ở lần render đầu tiên, hàm truyền vào useMemo được thực thi bình thường. Từ lần thứ hai trở đi, React kiểm tra mảng dependency:

  • Nếu dependency thay đổi: Hàm được chạy lại để lấy kết quả mới.

  • Nếu dependency giữ nguyên: React trả về kết quả đã lưu từ lần render trước — không làm lại bất cứ điều gì.

Ví Dụ Thực Tế

Bạn có danh sách 5.000 sản phẩm và cần lọc theo từ khóa tìm kiếm của người dùng. Đây là phép tính nặng — nhưng chỉ cần tính lại khi danh sách hoặc từ khóa thực sự thay đổi:

const filteredProducts = useMemo(
  () => filterLogic(products, query),
  [products, query]
);

3. useCallback: Ghi Nhớ Định Nghĩa Hàm

Trong khi useMemo cache giá trị, useCallback cache chính cái hàm bạn truyền vào — hàm đó không được thực thi ngay mà chỉ được nhớ lại.

Tại Sao Cần Ghi Nhớ Một Hàm?

Trong JavaScript, hàm là object. Mỗi khi Component re-render, hàm khai báo bên trong nó tạo ra một tham chiếu mới trong bộ nhớ — dù nội dung hàm không hề thay đổi.

Điều này trở thành vấn đề khi bạn truyền hàm đó xuống Component con đã được bọc trong React.memo. Vì hàm mới ≠ hàm cũ (so sánh theo tham chiếu), Component con re-render một cách vô ích.

Khi Nào useCallback Thực Sự Có Giá Trị?

  • Truyền hàm làm props cho Component con wrapped bởi React.memo.

  • Hàm là dependency của một Hook khác như useEffect.

4. Điểm Khác Nhau Then Chốt

Mục đích

  • useMemo: Cache một giá trị — số, chuỗi, mảng, object...

  • useCallback: Cache một hàm (function reference).

Giá trị trả về

  • useMemo: Kết quả sau khi callback được thực thi.

  • useCallback: Bản thân hàm callback, chưa được thực thi.

Mối liên hệ về cú pháp

Thực ra, useCallback(fn, deps) tương đương hoàn toàn với useMemo(() => fn, deps).

5. Năm Sai Lầm Phổ Biến Khi Dùng Memoization

Thói quen "bọc mọi thứ vào useMemo và useCallback" là một anti-pattern phổ biến. Dưới đây là những lý do thực tế bạn cần dừng lại:

Sai lầm 1: Chi phí so sánh dependency vượt quá lợi ích thu được

Việc so sánh mảng dependency sau mỗi render đôi khi tốn kém hơn cả việc tạo mới một hàm đơn giản. Memoization chỉ có lợi khi hàm thực sự nặng.

Sai lầm 2: Làm code trở nên khó đọc và khó bảo trì

Lạm dụng hook biến Component thành mê cung logic với vô số dependency. Đồng đội vào đọc sẽ không biết phải bắt đầu từ đâu.

Sai lầm 3: Tạo ra Stale Closures — bug khó tìm nhất

Nếu bạn quên thêm một biến vào mảng dependency, hàm sẽ đọc dữ liệu cũ — dẫn đến các bug logic âm thầm mà không có bất kỳ error nào trong console.

Sai lầm 4: Áp dụng với các giá trị nguyên thủy (primitive)

Các kiểu dữ liệu như number, string, boolean luôn được so sánh theo giá trị. Bọc chúng trong useMemo là thừa thãi hoàn toàn.

Sai lầm 5: Tối ưu trước khi đo lường

Đừng thêm Memoization vì "nghĩ là sẽ chậm". Hãy dùng React DevTools Profiler để xác định đúng điểm nghẽn cổ chai trước khi can thiệp.

6. Chiến Lược Tối Ưu Đúng Đắn

Quy trình thực tế tôi áp dụng trong các dự án production:

  1. Bước 1: Viết code tự nhiên, không nghĩ đến tối ưu hóa.

  2. Bước 2: Dùng React DevTools Profiler để tìm Component re-render nhiều nhất.

  3. Bước 3: Kiểm tra xem re-render đó có thực sự gây lag cho người dùng hay không. Nếu không — bỏ qua.

  4. Bước 4: Nếu cần tối ưu, thử tái cấu trúc Component (composition pattern) trước.

  5. Bước 5: Chỉ khi cần thiết — dùng useMemo cho tính toán nặng, useCallback cho hàm truyền xuống Component con.

7. Tổng Kết

useMemouseCallback là công cụ mạnh mẽ khi được sử dụng đúng chỗ, nhưng là gánh nặng không cần thiết khi bị lạm dụng. Hãy nhớ:

  • Dùng useMemo để bảo vệ kết quả của các phép tính đắt đỏ.

  • Dùng useCallback để duy trì tính nhất quán của tham chiếu hàm khi truyền xuống con.

  • Đo trước, tối ưu sau.

Bạn đã từng gặp trường hợp thêm useMemo vào mà App còn chậm hơn ban đầu chưa? Đó thường là dấu hiệu của sai lầm số 1 hoặc số 3 đã đề cập ở trên!

Câu hỏi thường gặp

Chia sẻ bài viết
Zalo

Bạn thấy bài viết hữu ích?

Liên hệ với chúng tôi để được tư vấn miễn phí về dịch vụ

Liên hệ ngay

Bài viết liên quan

Ảnh bìa bài viết: Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Frontend

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.

9/5/2026
Ảnh bìa bài viết: Promise Nâng Cao: all, allSettled, race, any — Khi Nào Dùng Cái Nào?
Frontend

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.

9/5/2026
Ảnh bìa bài viết: Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Frontend

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.

1/4/2026