React Hooks Deep Dive: useCallback, useMemo và useRef
Tìm hiểu chi tiết khi nào và cách sử dụng useCallback, useMemo, useRef đúng cách để tối ưu performance.
Minh Dev08 tháng 11 202411 phút4 thẻ
Share:
#react#performance#hooks#optimization
Nội dung chính
Khi Nào Thực Sự Cần Optimize?
Đừng optimize sớm. Trước khi dùng useCallback hay useMemo, hãy tự hỏi: component này có thực sự re-render quá nhiều không?
"Premature optimization is the root of all evil" — Donald Knuth
useCallback — Memoize Functions
tsx
// Không có useCallback — hàm mới được tạo mỗi render
function Parent() {
const [count, setCount] = useState(0)
// Mỗi render tạo ra một function reference mới
const handleClick = () => {
console.log('clicked')
}
return <Child onClick={handleClick} />
}
// Với useCallback — function được cache
function Parent() {
const [count, setCount] = useState(0)
// Chỉ tạo lại khi dependencies thay đổi
const handleClick = useCallback(() => {
console.log('clicked', count)
}, [count]) // dependency array
return <Child onClick={handleClick} />
}
// Child cần React.memo để tận dụng useCallback
const Child = React.memo(({ onClick }: { onClick: () => void }) => {
console.log('Child rendered')
return <button onClick={onClick}>Click</button>
})useMemo — Memoize Values
tsx
function ProductList({ products, filter }: Props) {
// BAD: Tính toán lại mỗi render
const filteredProducts = products.filter(p =>
p.category === filter && p.price > 100
)
// GOOD: Chỉ tính lại khi products hoặc filter thay đổi
const filteredProducts = useMemo(() =>
products.filter(p => p.category === filter && p.price > 100),
[products, filter]
)
return (
<ul>
{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}
</ul>
)
}Khi Nào Dùng useMemo?
tsx
// 1. Expensive computations
const sortedData = useMemo(() => {
return [...data].sort((a, b) => b.value - a.value)
}, [data])
// 2. Object/array references cho dependency arrays
const config = useMemo(() => ({
endpoint: '/api/data',
timeout: 5000,
}), []) // stable reference
// 3. KHÔNG cần useMemo cho đây
const name = useMemo(() => `${first} ${last}`, [first, last])
// Dùng thẳng: const name = `${first} ${last}`useRef — Không Trigger Re-render
tsx
function Timer() {
const [time, setTime] = useState(0)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const start = () => {
// Lưu giá trị mà không trigger re-render
intervalRef.current = setInterval(() => {
setTime(t => t + 1)
}, 1000)
}
const stop = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current)
}
}
return (
<div>
<span>{time}s</span>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
)
}DOM Manipulation với useRef
tsx
function AutoFocusInput() {
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
// Truy cập DOM node trực tiếp
inputRef.current?.focus()
}, [])
return <input ref={inputRef} placeholder="Auto focused!" />
}useRef để Lưu Previous Value
tsx
function usePrevious<T>(value: T): T | undefined {
const prevRef = useRef<T>()
useEffect(() => {
prevRef.current = value
})
return prevRef.current
}
// Usage
function Counter() {
const [count, setCount] = useState(0)
const prevCount = usePrevious(count)
return (
<p>Now: {count}, Before: {prevCount}</p>
)
}Tóm Tắt
| Hook | Dùng khi | Không dùng khi |
|------|----------|----------------|
| useCallback | Function được truyền vào React.memo component | Component không dùng React.memo |
| useMemo | Expensive computation, stable object references | Simple calculations |
| useRef | DOM access, mutable values không cần re-render | State cần trigger re-render |
Rule of thumb: Đo trước, optimize sau. Dùng React DevTools Profiler để xác định bottlenecks thực sự.
0Lượt xem