# React Hooks Nâng Cao: Mẫu Thiết Kế và Tối Ưu Hóa > Làm chủ React Hooks nâng cao với các mẫu đã được kiểm chứng. Custom hooks, useEffect tối ưu, useMemo, useCallback và kỹ thuật hiệu năng. - Published: 2026-02-16 - Updated: 2026-04-28 - Author: SharpSkill - Tags: react hooks, useEffect, custom hooks, performance, react patterns - Reading time: 12 min --- React Hooks đã thay đổi cách quản lý state và side effect trong các component hàm. Bên cạnh việc dùng `useState` và `useEffect` ở mức cơ bản, các mẫu thiết kế nâng cao cho phép tạo ra mã có thể tái sử dụng, hiệu năng cao và dễ bảo trì. > **Yêu cầu trước** > > Bài hướng dẫn giả định bạn đã quen với các Hooks cơ bản (useState, useEffect, useContext). Các ví dụ sử dụng React 18+ và TypeScript để có kiểu dữ liệu chặt chẽ. ## Làm chủ useEffect: tránh các bẫy thường gặp Hook `useEffect` thường bị hiểu sai và sử dụng không đúng cách. Hiểu sâu cách hoạt động của nó giúp tránh các bug khó nhận thấy và vấn đề về hiệu năng. Nguyên tắc cơ bản: `useEffect` đồng bộ một component với hệ thống bên ngoài. Nếu hiệu ứng không giao tiếp với thế giới bên ngoài (API, DOM, timer), rất có thể nó không cần thiết. ```tsx // hooks/useDocumentTitle.ts // Custom hook to synchronize document title import { useEffect } from 'react' export function useDocumentTitle(title: string) { useEffect(() => { // Save the previous title const previousTitle = document.title // Update the title document.title = title // Cleanup: restore previous title on unmount return () => { document.title = previousTitle } }, [title]) // Only trigger when title changes } ``` Lỗi phổ biến nhất là bỏ qua mảng dependency hoặc đưa vào những giá trị không ổn định. Mọi giá trị được dùng trong effect đều phải xuất hiện trong dependency. ```tsx // components/UserProfile.tsx 'use client' import { useEffect, useState } from 'react' interface User { id: string name: string email: string } interface UserProfileProps { userId: string } export function UserProfile({ userId }: UserProfileProps) { const [user, setUser] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) useEffect(() => { // Flag to prevent state updates after unmount let isMounted = true // Controller to cancel pending requests const controller = new AbortController() async function fetchUser() { setLoading(true) setError(null) try { const response = await fetch(`/api/users/${userId}`, { signal: controller.signal }) if (!response.ok) { throw new Error('User not found') } const data = await response.json() // Check if component is still mounted if (isMounted) { setUser(data) } } catch (err) { // Ignore abort errors if (err instanceof Error && err.name !== 'AbortError') { if (isMounted) { setError(err.message) } } } finally { if (isMounted) { setLoading(false) } } } fetchUser() // Cleanup: abort request and mark as unmounted return () => { isMounted = false controller.abort() } }, [userId]) // Re-run effect when userId changes if (loading) return
Loading...
if (error) return
Error: {error}
if (!user) return null return (

{user.name}

{user.email}

) } ``` Mẫu này với `AbortController` và cờ `isMounted` ngăn rò rỉ bộ nhớ và việc cập nhật state trên các component đã unmount. ## Tạo custom hooks tái sử dụng Custom hooks đóng gói logic có thể tái sử dụng. Một custom hook tốt tuân theo nguyên tắc trách nhiệm đơn lẻ và cung cấp một API rõ ràng. ```tsx // hooks/useLocalStorage.ts // Generic hook to persist state in localStorage import { useState, useEffect, useCallback } from 'react' export function useLocalStorage( key: string, initialValue: T ): [T, (value: T | ((prev: T) => T)) => void, () => void] { // Initialize state with stored value or default const [storedValue, setStoredValue] = useState(() => { if (typeof window === 'undefined') { return initialValue } try { const item = window.localStorage.getItem(key) return item ? JSON.parse(item) : initialValue } catch (error) { console.warn(`Error reading localStorage "${key}":`, error) return initialValue } }) // Sync with localStorage on every change useEffect(() => { if (typeof window === 'undefined') return try { window.localStorage.setItem(key, JSON.stringify(storedValue)) } catch (error) { console.warn(`Error writing localStorage "${key}":`, error) } }, [key, storedValue]) // Function to update value const setValue = useCallback((value: T | ((prev: T) => T)) => { setStoredValue((prev) => { const nextValue = value instanceof Function ? value(prev) : value return nextValue }) }, []) // Function to remove value const removeValue = useCallback(() => { setStoredValue(initialValue) if (typeof window !== 'undefined') { window.localStorage.removeItem(key) } }, [key, initialValue]) return [storedValue, setValue, removeValue] } ``` Hook này có thể dùng trong bất kỳ component nào để lưu dữ liệu lâu dài. ```tsx // components/ThemeToggle.tsx 'use client' import { useLocalStorage } from '@/hooks/useLocalStorage' type Theme = 'light' | 'dark' | 'system' export function ThemeToggle() { const [theme, setTheme] = useLocalStorage('theme', 'system') return ( ) } ``` > **Quy ước đặt tên** > > Custom hooks luôn bắt đầu bằng "use" (useLocalStorage, useFetch, useDebounce). Quy ước này cho phép React kiểm tra các quy tắc của hook và các công cụ linting hoạt động chính xác. ## Mẫu kết hợp với useReducer Với state phức tạp gồm nhiều hành động khả dĩ, `useReducer` mang lại cấu trúc dễ dự đoán hơn so với nhiều lệnh `useState` rời rạc. ```tsx // hooks/useCart.ts // Cart management hook with useReducer import { useReducer, useCallback, useMemo } from 'react' interface CartItem { id: string name: string price: number quantity: number } interface CartState { items: CartItem[] isOpen: boolean } type CartAction = | { type: 'ADD_ITEM'; payload: Omit } | { type: 'REMOVE_ITEM'; payload: string } | { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } } | { type: 'CLEAR_CART' } | { type: 'TOGGLE_CART' } function cartReducer(state: CartState, action: CartAction): CartState { switch (action.type) { case 'ADD_ITEM': { const existingItem = state.items.find( (item) => item.id === action.payload.id ) if (existingItem) { // Increment quantity if item exists return { ...state, items: state.items.map((item) => item.id === action.payload.id ? { ...item, quantity: item.quantity + 1 } : item ) } } // Add new item return { ...state, items: [...state.items, { ...action.payload, quantity: 1 }] } } case 'REMOVE_ITEM': return { ...state, items: state.items.filter((item) => item.id !== action.payload) } case 'UPDATE_QUANTITY': return { ...state, items: state.items.map((item) => item.id === action.payload.id ? { ...item, quantity: action.payload.quantity } : item ).filter((item) => item.quantity > 0) } case 'CLEAR_CART': return { ...state, items: [] } case 'TOGGLE_CART': return { ...state, isOpen: !state.isOpen } default: return state } } const initialState: CartState = { items: [], isOpen: false } export function useCart() { const [state, dispatch] = useReducer(cartReducer, initialState) // Memoized actions to avoid re-renders const addItem = useCallback( (item: Omit) => { dispatch({ type: 'ADD_ITEM', payload: item }) }, [] ) const removeItem = useCallback((id: string) => { dispatch({ type: 'REMOVE_ITEM', payload: id }) }, []) const updateQuantity = useCallback((id: string, quantity: number) => { dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } }) }, []) const clearCart = useCallback(() => { dispatch({ type: 'CLEAR_CART' }) }, []) const toggleCart = useCallback(() => { dispatch({ type: 'TOGGLE_CART' }) }, []) // Memoized derived values const total = useMemo( () => state.items.reduce( (sum, item) => sum + item.price * item.quantity, 0 ), [state.items] ) const itemCount = useMemo( () => state.items.reduce((sum, item) => sum + item.quantity, 0), [state.items] ) return { items: state.items, isOpen: state.isOpen, total, itemCount, addItem, removeItem, updateQuantity, clearCart, toggleCart } } ``` Cách tiếp cận này tập trung toàn bộ logic giỏ hàng và giúp viết unit test dễ dàng hơn. ## Tối ưu với useMemo và useCallback Các hook này tránh các tính toán hoặc việc tạo lại hàm không cần thiết. Cần dùng có chọn lọc: áp dụng tràn lan sẽ làm giảm hiệu năng thay vì cải thiện. ```tsx // components/ProductList.tsx 'use client' import { useState, useMemo, useCallback, memo } from 'react' interface Product { id: string name: string price: number category: string inStock: boolean } interface ProductListProps { products: Product[] } // Memoized child component const ProductCard = memo(function ProductCard({ product, onAddToCart }: { product: Product onAddToCart: (id: string) => void }) { console.log(`Render ProductCard: ${product.name}`) return (

{product.name}

${product.price}

) }) export function ProductList({ products }: ProductListProps) { const [filter, setFilter] = useState('') const [sortBy, setSortBy] = useState<'name' | 'price'>('name') const [cart, setCart] = useState([]) // useMemo: memoize result of expensive computation const filteredAndSortedProducts = useMemo(() => { console.log('Computing filteredAndSortedProducts') let result = products // Filter by name if (filter) { result = result.filter((p) => p.name.toLowerCase().includes(filter.toLowerCase()) ) } // Sort result = [...result].sort((a, b) => { if (sortBy === 'name') { return a.name.localeCompare(b.name) } return a.price - b.price }) return result }, [products, filter, sortBy]) // Recompute only when these deps change // useCallback: memoize a function const handleAddToCart = useCallback((productId: string) => { setCart((prev) => [...prev, productId]) }, []) // Empty deps: function never changes // Memoized statistics const stats = useMemo( () => ({ total: filteredAndSortedProducts.length, inStock: filteredAndSortedProducts.filter((p) => p.inStock).length, avgPrice: filteredAndSortedProducts.reduce((sum, p) => sum + p.price, 0) / filteredAndSortedProducts.length || 0 }), [filteredAndSortedProducts] ) return (
setFilter(e.target.value)} placeholder="Search..." className="px-3 py-2 border rounded-lg" />

{stats.total} products ({stats.inStock} in stock) - Average price: ${stats.avgPrice.toFixed(2)}

{filteredAndSortedProducts.map((product) => ( ))}
) } ``` > **Khi nào dùng useMemo/useCallback?** > > Các hook này làm tăng độ phức tạp. Hãy giới hạn cho: (1) các phép tính thực sự tốn kém, (2) prop được truyền vào component được memo bằng `memo()`, (3) dependency của các hook khác. ## Hook debounce cho ô tìm kiếm Debounce ngăn một hành động được thực thi quá thường xuyên, điển hình là khi gõ vào ô tìm kiếm. ```tsx // hooks/useDebounce.ts // Generic debounce hook import { useState, useEffect } from 'react' export function useDebounce(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value) useEffect(() => { // Create a timer that updates the value after delay const timer = setTimeout(() => { setDebouncedValue(value) }, delay) // Cleanup: cancel timer if value changes before delay return () => { clearTimeout(timer) } }, [value, delay]) return debouncedValue } ``` ```tsx // components/SearchInput.tsx 'use client' import { useState, useEffect } from 'react' import { useDebounce } from '@/hooks/useDebounce' interface SearchResult { id: string title: string } export function SearchInput() { const [query, setQuery] = useState('') const [results, setResults] = useState([]) const [loading, setLoading] = useState(false) // Debounce the query by 300ms const debouncedQuery = useDebounce(query, 300) useEffect(() => { // Don't search if query is empty if (!debouncedQuery.trim()) { setResults([]) return } async function search() { setLoading(true) try { const response = await fetch( `/api/search?q=${encodeURIComponent(debouncedQuery)}` ) const data = await response.json() setResults(data.results) } catch (error) { console.error('Search error:', error) } finally { setLoading(false) } } search() }, [debouncedQuery]) // Only triggered when debouncedQuery changes return (
setQuery(e.target.value)} placeholder="Search..." className="w-full px-4 py-2 border rounded-lg" /> {loading && (
)} {results.length > 0 && (
    {results.map((result) => (
  • {result.title}
  • ))}
)}
) } ``` Component chỉ thực hiện tìm kiếm sau 300 ms kể từ khi người dùng dừng gõ, qua đó tránh hàng chục yêu cầu không cần thiết. ## useImperativeHandle cho ref nâng cao Hook này cho phép component cha gọi các phương thức cụ thể của component con thông qua ref. ```tsx // components/VideoPlayer.tsx 'use client' import { useRef, useImperativeHandle, forwardRef, useState, useCallback } from 'react' // Interface of exposed methods export interface VideoPlayerRef { play: () => void pause: () => void seek: (time: number) => void getCurrentTime: () => number } interface VideoPlayerProps { src: string poster?: string } export const VideoPlayer = forwardRef( function VideoPlayer({ src, poster }, ref) { const videoRef = useRef(null) const [isPlaying, setIsPlaying] = useState(false) // Expose only desired methods to parent useImperativeHandle( ref, () => ({ play: () => { videoRef.current?.play() setIsPlaying(true) }, pause: () => { videoRef.current?.pause() setIsPlaying(false) }, seek: (time: number) => { if (videoRef.current) { videoRef.current.currentTime = time } }, getCurrentTime: () => { return videoRef.current?.currentTime ?? 0 } }), [] // No deps: methods always use current ref ) const handlePlayPause = useCallback(() => { if (isPlaying) { videoRef.current?.pause() } else { videoRef.current?.play() } setIsPlaying(!isPlaying) }, [isPlaying]) return (
) } ) ``` ```tsx // components/VideoController.tsx 'use client' import { useRef } from 'react' import { VideoPlayer, VideoPlayerRef } from './VideoPlayer' export function VideoController() { const playerRef = useRef(null) const handleSkipForward = () => { if (playerRef.current) { const currentTime = playerRef.current.getCurrentTime() playerRef.current.seek(currentTime + 10) } } return (
) } ``` Mẫu này đặc biệt hữu ích cho component đa phương tiện, biểu mẫu hoặc bất kỳ giao diện nào cần điều khiển dạng mệnh lệnh. ## Hook quản lý vòng đời request Một hook nâng cao để xử lý các trạng thái loading, lỗi và dữ liệu với kiểu dữ liệu chặt chẽ. ```tsx // hooks/useFetch.ts // Generic hook for HTTP requests import { useState, useEffect, useCallback, useRef } from 'react' interface UseFetchState { data: T | null loading: boolean error: Error | null } interface UseFetchOptions { immediate?: boolean // Execute immediately on mount onSuccess?: (data: T) => void onError?: (error: Error) => void } export function useFetch( url: string, options: UseFetchOptions = {} ) { const { immediate = true, onSuccess, onError } = options const [state, setState] = useState>({ data: null, loading: immediate, error: null }) // Ref to prevent updates after unmount const mountedRef = useRef(true) // Ref for abort controller const abortControllerRef = useRef(null) const execute = useCallback(async () => { // Abort previous request if in progress abortControllerRef.current?.abort() abortControllerRef.current = new AbortController() setState((prev) => ({ ...prev, loading: true, error: null })) try { const response = await fetch(url, { signal: abortControllerRef.current.signal }) if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`) } const data: T = await response.json() if (mountedRef.current) { setState({ data, loading: false, error: null }) onSuccess?.(data) } return data } catch (error) { // Ignore abort errors if (error instanceof Error && error.name === 'AbortError') { return null } const err = error instanceof Error ? error : new Error('Unknown error') if (mountedRef.current) { setState({ data: null, loading: false, error: err }) onError?.(err) } return null } }, [url, onSuccess, onError]) const reset = useCallback(() => { setState({ data: null, loading: false, error: null }) }, []) // Execute on mount if immediate=true useEffect(() => { if (immediate) { execute() } return () => { mountedRef.current = false abortControllerRef.current?.abort() } }, [execute, immediate]) return { ...state, execute, reset, isIdle: !state.loading && !state.data && !state.error } } ``` Hook này cung cấp một API hoàn chỉnh để xử lý mọi request, có hủy tự động và quản lý vòng đời. ## Kết luận Các mẫu React Hooks nâng cao giúp tạo ra mã dễ bảo trì và hiệu năng cao. Những điểm chính cần ghi nhớ: - ✅ **useEffect**: luôn có cleanup và xử lý race condition bằng AbortController - ✅ **Custom hooks**: đóng gói logic tái sử dụng với API rõ ràng - ✅ **useReducer**: ưu tiên cho state phức tạp với nhiều hành động - ✅ **useMemo/useCallback**: dùng có chọn lọc, chỉ khi cần thiết - ✅ **useDebounce**: hạn chế thực thi quá thường xuyên trong tìm kiếm - ✅ **useImperativeHandle**: phơi bày phương thức mệnh lệnh một cách có kiểm soát Làm chủ các mẫu này tạo nên sự khác biệt giữa lập trình viên React trung cấp và chuyên gia. Mỗi hook giải quyết một vấn đề cụ thể: chọn đúng công cụ vào đúng thời điểm là chìa khóa cho một kiến trúc React vững chắc. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/vi/blog/react-next/advanced-react-hooks-patterns-optimizations