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 Hooks Deep Dive: useCallback, useMemo và useRef
#react#performance#hooks#optimization

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

Bài Viết Liên Quan

Bình luận(0)

Đăng nhập để tham gia thảo luận