0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

🔥 Khám Phá useCallback: Khắc Phục Vấn Đề Memoization Trong React

Đăng vào 5 tháng trước

• 3 phút đọc

🔥 Khám Phá useCallback: Khắc Phục Vấn Đề Memoization Trong React

Nếu bạn đã làm việc với React và tối ưu hóa hiệu suất, chắc hẳn bạn đã nghe nói đến React.memo, useMemo, và useCallback. Tuy nhiên, hầu hết các hướng dẫn chỉ cung cấp ví dụ bề mặt mà không đi sâu vào vấn đề. Hôm nay, chúng ta sẽ tìm hiểu sâu hơn về useCallback — cách JavaScript xử lý tham chiếu hàm, lý do khiến chúng gây ra việc tái render không cần thiết và cách React khắc phục vấn đề này ở cấp độ nội bộ.

🧠 Vấn Đề Cơ Bản: Hàm Là Đối Tượng

Trong JavaScript, hàm là đối tượng. Điều này có nghĩa là mỗi khi bạn khai báo một hàm bên trong một component, một đối tượng hàm mới sẽ được tạo ra trong bộ nhớ mỗi lần render. Ngay cả khi hai hàm trông giống nhau, chúng không chia sẻ cùng một tham chiếu.
👉 Điều này trở thành vấn đề khi truyền hàm dưới dạng props.

🔴 Không Có useCallback: Tham Chiếu Hàm Phá Vỡ Memoization

👉 Điều gì xảy ra ở đây?

  • Component cha sẽ tái render mỗi khi count thay đổi.
  • Mỗi lần render tạo ra một tham chiếu hàm mới cho handleClick.
  • React.memo trong component con so sánh props → thấy onClick đã thay đổi (tham chiếu mới).
  • Component con tái render không cần thiết.

🟢 Với useCallback: Tham Chiếu Hàm Ổn Định

Bây giờ, component con không tái render trừ khi thực sự cần thiết. 🎉

⚡ Cách useCallback Hoạt Động Nội Bộ

javascript Copy
const fn = useCallback(callback, deps);
  1. React thực hiện các bước sau:
    • Kiểm tra xem có hàm trước đó được lưu trữ trong trạng thái hook hay không.
    • So sánh mảng deps mới với mảng deps cũ (so sánh nông).
      • Nếu deps giống nhau → tái sử dụng tham chiếu hàm cũ.
      • Nếu deps thay đổi → tạo và lưu trữ tham chiếu hàm mới.
javascript Copy
function useCallback(fn, deps) {
  const last = getHookState(); // { fn, deps }
  if (last && shallowEqual(last.deps, deps)) {
    return last.fn; // tái sử dụng tham chiếu
  }
  setHookState({ fn, deps });
  return fn; // tham chiếu mới
}

Vậy nên useCallback thực sự là một bộ nhớ cache cho tham chiếu hàm.

🎯 Bí Ẩn Của Mảng Rỗng []

Khi bạn viết:

javascript Copy
const fn = useCallback(() => {
  console.log("Hello");
}, []);
  • [] → không có dependencies.
  • React sẽ tạo hàm một lần và luôn trả về cùng một tham chiếu.
  • Hoàn hảo cho các callback không phụ thuộc vào trạng thái hoặc props nào.

Nhưng ⚠️ hãy cẩn thận với bẫy closure cũ.

🕳️ Bẫy Closure

javascript Copy
const [count, setCount] = useState(0);

const logCount = useCallback(() => {
  console.log(count);
}, []); // deps rỗng
  • Lần render đầu tiên: count = 0.
  • Hàm được tạo với count = 0.
  • Ngay cả khi count cập nhật → hàm vẫn giữ giá trị cũ.

Vì thế, các log sẽ luôn hiển thị 0.

✅ Sửa lỗi bằng cách thêm dependencies:

javascript Copy
const logCount = useCallback(() => {
  console.log(count);
}, [count]); // cập nhật với count

Bây giờ, hàm sẽ được tạo lại mỗi khi count thay đổi.

⚖️ Khi Nào Nên Sử Dụng useCallback

✅ Khi truyền hàm cho các component con React.memo.
✅ Khi hàm là dependencies của các hook khác (useEffect, useMemo).
❌ Không nên bọc mọi hàm một cách mù quáng → gây overhead không cần thiết.

🚀 Tóm Tắt Cuối Cùng

  • Hàm trong JS là đối tượng → tham chiếu mới mỗi lần render.
  • React so sánh tham chiếu, không phải nội dung.
  • useCallback ổn định hóa các tham chiếu hàm cho đến khi các dependencies thay đổi.
  • [] = không bao giờ thay đổi (nhưng cẩn thận với closures cũ).
  • Hoạt động tốt nhất với React.memo để ngăn chặn việc tái render của các component con.

🔗 Nếu bạn đã nắm vững useMemoReact.memo, thì useCallback chính là mảnh ghép còn thiếu để hoàn thiện bộ ba tối ưu hóa.

👉 Lần sau khi component con của bạn cứ tái render dù “không có gì thay đổi” — hãy kiểm tra xem bạn có quên useCallback hay không.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào