Mục lục

Memoization: Dùng sai cách là liều thuốc độc

Phân tích chi phí bộ nhớ của useMemo/useCallback và chiến lược tối ưu hóa dựa trên Profiling thay vì phỏng đoán.

Việc sử dụng useMemouseCallback bừa bãi là một trong những nguyên nhân phổ biến gây suy giảm hiệu năng trong các ứng dụng React quy mô lớn.

Bài viết này đi sâu vào chi phí thực sự của quá trình Memoization và định nghĩa lại chiến lược tối ưu hóa.

1. Chi phí ẩn của Memoization

Mọi sự tối ưu đều có sự đánh đổi (Trade-off). Khi bạn sử dụng Memoization, bạn đang đánh đổi Memory (Bộ nhớ) để lấy CPU (Tốc độ tính toán).

Khi useMemo được khởi tạo:

  1. Memory Allocation: React phải cấp phát bộ nhớ để lưu giữ giá trị cũ và mảng dependencies.
  2. Garbage Collection Overhead: Các object dependencies không thể được giải phóng bộ nhớ ngay lập tức vì React đang giữ tham chiếu đến chúng.

Code Analysis

tsx:
// Inefficient: Chi phí Memory allocation lớn hơn chi phí tính toán
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);

Trong ví dụ trên, engine V8 của JavaScript có thể thực hiện phép cộng chuỗi (string concatenation) cực kỳ nhanh (vài nano-seconds). Việc tạo ra hook, lưu array [firstName, lastName] và so sánh chúng trong mỗi lần render tốn kém tài nguyên hơn chính phép tính đó.

2. Chiến lược Memoization hiệu quả

Chỉ áp dụng Memoization khi thỏa mãn một trong hai điều kiện sau:

Điều kiện A: Chi phí tính toán đắt đỏ (Expensive Computation)

Phép tính làm chặn Main Thread quá 16ms (1 frame 60fps).

  • Ví dụ: Filter/Sort danh sách > 1000 items.
  • Ví dụ: Cryptographic hashing, parsing dữ liệu lớn.

Điều kiện B: Referential Equality (Tính nhất quán tham chiếu)

Đây là lý do chính đáng nhất trong React. Bạn cần useMemo để trả về cùng một tham chiếu object/array, nhằm tránh trigger re-render ở các component con được bọc React.memo.

tsx:
// Optimization Pattern: Stable Reference
const ChartComponent = React.memo(({ data, config }) => { ... });

function Dashboard({ rawData }) {
  // Bắt buộc dùng useMemo, nếu không ChartComponent sẽ re-render
  // mỗi lần Dashboard render vì object config mới được tạo ra.
  const chartConfig = useMemo(() => ({
    responsive: true,
    color: 'blue'
  }), []);

  return <ChartComponent data={rawData} config={chartConfig} />;
}

3. Engineering Case Study: Instagram Web

Tại Facebook/Instagram, các kỹ sư đã nhận thấy rằng việc sử dụng useCallback cho mọi function prop gây ra hiện tượng Memory Pressure nghiêm trọng trên các thiết bị mobile cấu hình thấp.

Giải pháp: Họ loại bỏ hầu hết các useCallback không cần thiết và chỉ giữ lại ở những điểm then chốt (như Virtualized Lists hoặc các Animation components nặng).

Kết quả:

  • Tốc độ khởi động app (TTI) nhanh hơn.
  • Giảm lượng RAM tiêu thụ đáng kể.
  • Việc Re-render nhẹ (Cheap Re-renders) được chấp nhận thay vì gánh nặng quản lý dependencies.

4. Quy trình tối ưu hóa (Optimization Workflow)

  1. Measure First: Không tối ưu hóa sớm (Premature Optimization).
  2. Identify Bottlenecks: Sử dụng React Profiler để tìm các component render > 16ms.
  3. Apply Fix:
    • Ưu tiên Composition (State colocation, pass components as children) trước.
    • Sau đó mới dùng React.memo + useMemo.
  4. Verify: Kiểm tra lại bằng Profiler để đảm bảo Memoization thực sự giảm thời gian render (đôi khi nó làm chậm đi do overhead).

Kết luận

Memoization là công cụ chính xác, không phải là thói quen mặc định. Hãy coi mỗi useMemo là một món nợ kỹ thuật cần được biện minh bằng số liệu đo lường cụ thể.

Quảng cáo
mdhorizontal