React Memoization Visualized: useMemo, useCallback và React.memo
Ngày 30 tháng 3, 2026
Memoization trong React không phải là "bật lên là nhanh". Nó là bài toán trade-off giữa chi phí so sánh, chi phí cache và chi phí re-render.
Bài này đi theo hướng trực quan để trả lời 3 câu hỏi:
React.memochặn re-render theo điều kiện nào?useCallbackgiúp gì choReact.memo?useMemothực sự cache cái gì và khi nào invalid?
REACT MEMOIZATION DECISION MAP
Problem
problem
React.memo
memo
useCallback
callback
useMemo
memoValue
Result
result
Parent re-renders propagate to children and expensive calculations.
Caching only works when dependency arrays are accurate and prop references are stable.
Apply memoization only to proven hot paths, not everywhere by default.
1. Vấn đề gốc: vì sao component re-render quá nhiều?
Trong React, khi parent render lại, child cũng thường render lại theo cây.
Điều này không luôn xấu, nhưng sẽ tốn tài nguyên khi:
- child nặng,
- props giữ nguyên,
- hoặc có phép tính đắt đỏ lặp đi lặp lại.
Memoization giúp React "bỏ qua công việc không cần thiết" trong các trường hợp đó.
2. React.memo: chặn render nếu props không đổi
React.memo(Component) sẽ shallow compare props cũ/mới.
Nếu props bằng nhau theo shallow equality, React có thể skip render component đó.
const UserCard = React.memo(function UserCard({ user }) {
return <div>{user.name}</div>;
});Lưu ý quan trọng:
React.memokhông phải "never render again".- Nó chỉ skip khi props của chính component đó không đổi.
- Nếu props là object/function mới mỗi lần render, memo khó phát huy hiệu quả.
2.1 React so sánh props bằng gì?
Mặc định, React.memo làm shallow compare từng prop bằng logic gần với Object.is.
Hiểu nhanh:
- Primitive (
number,string,boolean,null,undefined) so sánh theo giá trị. - Object/array/function so sánh theo reference, không so sánh sâu nội dung.
Vì vậy:
count={1}qua nhiều render vẫn dễ được coi là "không đổi".{a: 1}tạo mới mỗi render là reference mới, nên bị coi là "đã đổi" dù nội dung giống nhau.() => doSomething()tạo mới mỗi render cũng là function reference mới.
Ví dụ gây fail memo:
<Child config={{ pageSize: 20 }} onSave={() => save(id)} />Dù pageSize và logic save không đổi, child vẫn có thể re-render vì cả config và onSave đều là reference mới.
2.2 Cách ổn định object/function prop
- Dùng
useMemocho object/array prop. - Dùng
useCallbackcho function prop.
const config = useMemo(() => ({ pageSize: 20 }), []);
const onSave = useCallback(() => save(id), [id]);
return <Child config={config} onSave={onSave} />;2.3 Hàm comparator custom trong React.memo (areEqual / isEqual)
React.memo cho phép truyền comparator custom làm tham số thứ hai:
const Child = React.memo(
function Child(props) {
return <div>{props.user.name}</div>;
},
(prevProps, nextProps) => {
return prevProps.user.id === nextProps.user.id;
},
);Quy tắc quan trọng (dễ nhầm):
- Comparator trả về
true=> React bỏ qua render. - Comparator trả về
false=> React render lại.
Bạn có thể nghe tên isEqual, areEqual, hoặc dùng thư viện deep compare như lodash/isEqual, nhưng cần rất cẩn thận:
- Deep compare có chi phí CPU, có thể đắt hơn chính render.
- Comparator sai logic dễ làm UI stale (không render khi đáng ra phải render).
- Nếu comparator bỏ qua function prop, có thể giữ callback cũ và dính stale closure.
Thực dụng nhất:
- Ưu tiên ổn định reference bằng
useMemo/useCallbacktrước. - Chỉ dùng comparator custom cho hotspot đã profile rõ.
- Giữ comparator ngắn, dễ chứng minh tính đúng.
3. useCallback: giữ ổn định reference của function prop
React.memo thường "thất bại" vì function prop bị tạo mới mỗi lần parent render.
const onSave = () => save(id); // function mới ở mỗi renderKhi đó child nhận prop mới, shallow-compare fail, child render lại.
useCallback giúp giữ identity function ổn định theo dependency:
const onSave = useCallback(() => save(id), [id]);USECALLBACK + REACT.MEMO IN PRACTICE
parent tick: 0
actions: 0
memo child with inline callback
Child render count: 1
memo child with stable callback
Child render count: 1
Parent re-renders change inline function identity, which breaks memo skip. useCallback keeps identity stable.
Checklist nhanh:
- Có truyền callback xuống child memoized không?
- Parent có re-render vì state unrelated không?
- Callback đó có thể ổn định reference bằng
useCallbackkhông?
4. useMemo: cache giá trị tính toán đắt đỏ
useMemo memoize kết quả của một hàm tính toán.
const filtered = useMemo(() => {
return products.filter((p) => p.name.includes(keyword));
}, [products, keyword]);Nó hữu ích khi:
- phép tính có chi phí đáng kể,
- dependency ít đổi,
- cùng giá trị được tái sử dụng qua nhiều render.
USEMEMO CACHE VS RE-RENDER NOISE
Compute count: 1
React, Redux, Recoil, Relay, Remix
Compute count: 1
React, Redux, Recoil, Relay, Remix
Toggle unrelated state to see that useMemo keeps cached value until keyword dependency changes.
Điểm dễ nhầm:
useMemokhông đảm bảo performance trong mọi tình huống.- Tự nó cũng có chi phí quản lý dependency + cache.
- Khi dependency đổi, cache bị invalidation.
- Dùng tràn lan có thể làm code khó đọc hơn lợi ích thực tế.
5. Kết hợp 3 công cụ đúng cách
Mô hình thường gặp trong production:
- Child nặng được bọc
React.memo. - Callback truyền xuống child dùng
useCallback. - Dữ liệu derived list/map dùng
useMemo.
Ví dụ:
const Row = React.memo(function Row({ item, onSelect }) {
return <button onClick={() => onSelect(item.id)}>{item.name}</button>;
});
function ProductList({ products, keyword }) {
const filtered = useMemo(
() => products.filter((p) => p.name.includes(keyword)),
[products, keyword],
);
const handleSelect = useCallback((id: string) => {
console.log("select", id);
}, []);
return (
<div>
{filtered.map((item) => (
<Row key={item.id} item={item} onSelect={handleSelect} />
))}
</div>
);
}6. Khi nào KHÔNG nên memoize?
- Component nhỏ, render nhanh, tần suất thấp.
- Chưa có số liệu profiling.
- Dependency thay đổi liên tục khiến cache gần như vô dụng.
- Team phải trả giá lớn về độ phức tạp code.
Quy tắc thực dụng:
- Profile trước.
- Tối ưu đúng điểm nghẽn.
- Đo lại sau khi tối ưu.
Thuật ngữ nhanh (Glossary)
shallow compare: so sánh ở level prop trực tiếp, không đi sâu object lồng nhau.reference: địa chỉ object/function trong bộ nhớ.identity: danh tính reference của một giá trị qua các lần render.stale: dữ liệu/UI cũ không phản ánh trạng thái mới nhất.stale closure: callback giữ biến từ render cũ.deep compare: so sánh sâu toàn bộ cấu trúc dữ liệu.invalidation: hủy cache cũ khi dependency đổi.hotspot: điểm nghẽn hiệu năng đáng ưu tiên.profile/profiling: đo hiệu năng để biết đúng điểm cần tối ưu.trade-off: đánh đổi chi phí/lợi ích khi tối ưu.
Tổng kết
React.memo, useCallback, useMemo là bộ ba cực mạnh nếu dùng đúng ngữ cảnh.
Hãy nhớ:
React.memotối ưu render của component.useCallbacktối ưu identity của function prop.useMemotối ưu kết quả tính toán.
Tối ưu hiệu năng trong React nên bắt đầu từ dữ liệu đo đạc, không bắt đầu từ cảm giác.