Khi mới bắt đầu với React, useState giống như câu thần chú giúp mọi thứ hoạt động trơn tru. Bạn muốn một nút theo dõi số lần nhấp? Hãy dùng useState. Bạn cần chuyển đổi trạng thái hiển thị của một modal? useState lại được gọi tên. Nhưng khi bạn đi sâu hơn vào thế giới phát triển React, bạn có thể bắt đầu tự hỏi: Liệu useState có phải là lựa chọn phù hợp cho mọi tình huống?
Câu trả lời, không có gì ngạc nhiên, đó là không. Mặc dù useState rất linh hoạt, React cung cấp các hook và mô hình khác có thể phù hợp hơn tùy thuộc vào nhu cầu cụ thể của bạn. Hãy cùng khám phá một số lựa chọn thay thế như useRef, useReducer và useContext để xem khi nào chúng thực sự hữu ích.
useRef: Lựa chọn thay thế useState khi không cần re-render
Một sai lầm phổ biến của người mới bắt đầu với React là sử dụng useState cho các giá trị không thực sự ảnh hưởng đến quá trình render. useRef là lựa chọn lý tưởng khi bạn cần duy trì dữ liệu qua các lần render mà không kích hoạt re-render.
Ví dụ thực tế:
Hãy tưởng tượng bạn đang theo dõi số lần nhấp vào một nút, nhưng component không cần phải re-render mỗi khi số lần nhấp thay đổi.
function ClickTracker() { const clickCount = useRef(0); const handleClick = () => { clickCount.current += 1; console.log(`Button clicked ${clickCount.current} times`); }; return <button onClick={handleClick}>Click me</button>;
}
Trong trường hợp này, useRef lưu giữ số lần nhấp mà không gây ra re-render không cần thiết. Nếu bạn sử dụng useState, component sẽ re-render sau mỗi lần nhấp, điều này là không cần thiết trong tình huống này.
Vậy khi nào nên chọn useRef:
- Theo dõi các giá trị không cần kích hoạt cập nhật giao diện người dùng.
- Lưu trữ tham chiếu đến các phần tử DOM hoặc các giá trị trạng thái trước đó.
useReducer: Giải pháp cho logic trạng thái phức tạp
Đối với logic trạng thái phức tạp hơn, đặc biệt là khi trạng thái của bạn liên quan đến nhiều giá trị con hoặc hành động, useReducer có thể là một giải pháp thay thế mạnh mẽ. useState có thể bắt đầu trở nên cồng kềnh khi bạn quản lý một số phần tử trạng thái phụ thuộc lẫn nhau.
Một tình huống thực tế:
Giả sử bạn đang xây dựng một biểu mẫu, nơi bạn quản lý một số input như tên, email và mật khẩu. Việc sử dụng useState cho mỗi input có thể nhanh chóng trở nên tẻ nhạt.
function formReducer(state, action) { switch (action.type) { case 'SET_NAME': return { ...state, name: action.payload }; case 'SET_EMAIL': return { ...state, email: action.payload }; case 'SET_PASSWORD': return { ...state, password: action.payload }; default: return state; }
} function SignupForm() { const [formState, dispatch] = useReducer(formReducer, { name: '', email: '', password: '' }); return ( <> <input value={formState.name} onChange={(e) => dispatch({ type: 'SET_NAME', payload: e.target.value })} placeholder="Name" /> <input value={formState.email} onChange={(e) => dispatch({ type: 'SET_EMAIL', payload: e.target.value })} placeholder="Email" /> <input value={formState.password} onChange={(e) => dispatch({ type: 'SET_PASSWORD', payload: e.target.value })} placeholder="Password" /> </> );
}
Ở đây, useReducer tập trung tất cả các cập nhật trạng thái vào một hàm duy nhất, giúp việc quản lý dễ dàng hơn so với nhiều lệnh gọi useState.
Vậy khi nào nên chọn useReducer:
- Xử lý logic trạng thái phức tạp với nhiều giá trị con hoặc hành động.
- Khi các thay đổi trạng thái tuân theo một luồng dựa trên hành động rõ ràng (ví dụ: SET, ADD, REMOVE).
useContext: Chia sẻ trạng thái giữa các component hiệu quả
Nếu trạng thái của bạn được chia sẻ trên nhiều component, việc truyền props xuống nhiều cấp có thể nhanh chóng trở thành một cơn ác mộng. Đó là lúc useContext phát huy tác dụng — nó giúp bạn chia sẻ trạng thái mà không cần truyền props qua nhiều cấp.
Ví dụ về useContext:
Hãy tưởng tượng bạn đang xây dựng một giỏ hàng. Bạn cần trạng thái của giỏ hàng (các mặt hàng đã thêm, tổng giá, v.v.) có thể truy cập được ở các phần khác nhau của ứng dụng — có thể là tiêu đề, trang thanh toán và bản xem trước giỏ hàng.
const CartContext = React.createContext(); function CartProvider({ children }) { const [cart, setCart] = useState([]); return ( <CartContext.Provider value={{ cart, setCart }}> {children} </CartContext.Provider> );
} function Header() { const { cart } = React.useContext(CartContext); return <div>Items in cart: {cart.length}</div>;
} function App() { return ( <CartProvider> <Header /> {/* Other components */} </CartProvider> );
}
Trong kịch bản này, useContext làm cho trạng thái giỏ hàng có sẵn cho bất kỳ component nào cần đến nó mà không cần truyền props theo cách thủ công.
Vậy khi nào nên chọn useContext:
- Chia sẻ trạng thái giữa các component được lồng sâu.
- Tránh việc truyền props cho dữ liệu toàn cục được truy cập phổ biến (ví dụ: xác thực người dùng, chủ đề).
Một cách tiếp cận phù hợp
Mặc dù useState là điểm khởi đầu tuyệt vời, thế nhưng hệ sinh thái của React có thể cung cấp các công cụ mạnh mẽ khác như useRef, useReducer, và useContext có thể giúp đơn giản hóa mã của bạn và cải thiện hiệu suất đáng kể. Thay vì sử dụng useState mặc định, hãy tự hỏi bản thân bạn một vài câu hỏi chính:
- Trạng thái này có cần kích hoạt kết xuất lại không? (Nếu không, hãy cân nhắc useRef)
- Logic trạng thái của tôi có trở nên quá phức tạp không useState? (Thử useReducer)
- Tôi có đang truyền đạo cụ qua quá nhiều thành phần không? (Xem xét useContext)
Bằng cách chọn đúng công cụ cho công việc, bạn sẽ viết được các thành phần React hiệu quả hơn, dễ bảo trì hơn và dễ lập luận hơn.
Vì vậy, lần tới khi bạn thấy mình vẫn đang mặc định sử dụng useState, hãy dừng lại một lát. Có thể sẽ có một cách tốt hơn để xử lý mọi việc!