useContext là một hook React cung cấp cho các component chức năng quyền truy cập trực tiếp vào dữ liệu từ context được tạo qua createContext. Context trong React giải quyết vấn đề props drilling — truyền props qua nhiều component trung gian mà bản thân chúng không sử dụng dữ liệu này. Theo React Documentation (2025), useContext nhận một đối tượng context và trả về giá trị hiện tại được thiết lập bởi Provider gần nhất ở phía trên trong cây component. Khi giá trị trong Provider thay đổi, tất cả các component sử dụng useContext tự động kết xuất lại.
Những điểm chính
useContext là một hook được thêm vào React 16.8 cùng với các hook khác cho phép đọc giá trị từ context React. Context là một cơ chế được tích hợp trong React được thiết kế để truyền dữ liệu qua cây component mà không cần truyền props thủ công ở mỗi cấp độ. useContext thay thế component Consumer từ Context API cũ và làm cho mã ngắn gọn và dễ đọc hơn.
Các trường hợp sử dụng điển hình của context bao gồm chủ đề (sáng/tối), ngôn ngữ và bản dịch (i18n), xác thực người dùng, cài đặt ứng dụng và bất kỳ dữ liệu toàn cục nào khác mà nhiều component ở các cấp độ lồng nhau khác nhau cần. Nhóm React khuyến nghị sử dụng context cho dữ liệu toàn cục cho một cây con của các component, nhưng không phải cho toàn bộ ứng dụng.
Theo React Team — Tài liệu context (2025), việc sử dụng context không đúng cách là một trong những nguyên nhân chính gây ra vấn đề hiệu suất trong các ứng dụng React. Mỗi lần thay đổi giá trị trong Provider gây ra kết xuất lại tất cả người tiêu dùng, bất kể phần nào của dữ liệu đã thay đổi. Tối ưu hóa thông qua useMemo và chia nhỏ context giải quyết vấn đề này.
import { createContext, useContext } from 'react';
// Tạo context với giá trị mặc định
const ThemeContext = createContext('sáng');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={`btn-${theme}`}>Click</button>;
}
Cơ chế context trong React được triển khai thông qua mẫu Provider-Consumer. createContext trả về một đối tượng với hai thực thể: Provider — một component truyền giá trị, và chính đối tượng context, được sử dụng trong useContext. Provider được gắn trong cây component và truyền giá trị đến tất cả các phần tử con bất kể độ sâu lồng nhau.
Khi React gặp một lời gọi useContext, nó duyệt qua cây fiber để tìm Provider gần nhất cho context đó. Nếu tìm thấy Provider, giá trị của nó được trả về. Nếu không tìm thấy Provider, giá trị mặc định được truyền cho createContext sẽ được trả về. Quá trình tìm kiếm này diễn ra ở mỗi lần kết xuất, nhưng nhờ bộ nhớ đệm nút fiber, nó rất nhanh và không ảnh hưởng đến hiệu suất.
Theo React — Nội bộ context (2024), triển khai nội bộ của useContext sử dụng một danh sách liên kết các hook, tương tự như useState. Mỗi hook lưu trữ một tham chiếu đến một nút fiber, cho phép React nhanh chóng xác định Provider nào tương ứng với context đó. Khi một Provider cập nhật giá trị của nó, React đánh dấu tất cả các nút fiber sử dụng context đó để kết xuất lại.
Các component Provider có thể được lồng vào nhau, tạo ra một hệ thống phân cấp các context. Mỗi Provider con ghi đè giá trị của cha cho cây con của riêng nó. Điều này hữu ích khi một màn hình cần chủ đề sáng trong khi một cửa sổ modal lồng nhau cần chủ đề tối. useContext luôn trả về giá trị của Provider gần nhất ở phía trên trong cây.
const UserContext = createContext(null);
const ThemeContext = createContext('sáng');
function App() {
return (
<UserContext.Provider value={{ name: 'Alice' }}>
<ThemeContext.Provider value='dark'>
<Profile />
</ThemeContext.Provider>
</UserContext.Provider>
);
}
Hàm createContext(defaultValue) tạo một đối tượng context. Tham số defaultValue được sử dụng khi một component gọi useContext nhưng không có Provider tương ứng ở phía trên trong cây. Nếu không có defaultValue, useContext sẽ trả về undefined, điều này có thể dẫn đến lỗi không mong muốn. Khuyến nghị luôn truyền một giá trị mặc định có ý nghĩa hoặc null.
Tạo một provider tùy chỉnh là một mẫu phổ biến để đóng gói logic context. Bên trong provider như vậy, trạng thái được lưu trữ (qua useState hoặc useReducer) và được cung cấp thông qua prop value của Provider. Điều này ẩn chi tiết triển khai khỏi các component tiêu dùng và tập trung logic quản lý context vào một nơi.
// Provider tùy chỉnh với quản lý trạng thái
const AuthContext = createContext(null);
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = useCallback(async (email, pass) => {
const u = await loginApi(email, pass);
setUser(u);
}, []);
return (
<AuthContext.Provider value={{ user, login }}>
{children}
</AuthContext.Provider>
);
}
Trong các component chức năng, useContext là cách duy nhất để truy cập context. Nó thay thế component Consumer từ Context API cũ, vốn yêu cầu mẫu render-prop: <ThemeContext.Consumer>{value => ...}</ThemeContext.Consumer>. useContext làm cho mã tuyến tính và dễ đọc hơn, đặc biệt khi làm việc với nhiều context trong một component.
Khi sử dụng nhiều context trong một component, chỉ cần gọi useContext nhiều lần cho mỗi context. Mỗi lần gọi trả về giá trị của Provider tương ứng. Thứ tự gọi không quan trọng, vì mỗi context là một thực thể độc lập. React tối ưu hóa nhiều lời gọi thông qua cùng một hệ thống tham chiếu fiber.
function Dashboard() {
const { user } = useContext(AuthContext);
const theme = useContext(ThemeContext);
const { locale } = useContext(I18nContext);
return (
<div className={`dashboard-${theme}`}>
<h1>{locale.greeting}, {user.name}</h1>
</div>
);
}
Sự lựa chọn giữa useContext và Redux phụ thuộc vào quy mô và độ phức tạp của việc quản lý trạng thái. useContext + useReducer là một sự thay thế nhẹ cho Redux cho các ứng dụng nhỏ và vừa. Nó không yêu cầu cài đặt thư viện bên ngoài, dễ học hơn và đủ cho hầu hết các tác vụ. Redux được biện minh khi cần một kiến trúc chặt chẽ với middleware, công cụ phát triển và cập nhật bất biến.
Lợi thế chính của Redux so với useContext là tối ưu hóa kết xuất lại. Theo mặc định, khi giá trị trong Provider thay đổi, tất cả người tiêu dùng context đều kết xuất lại. Redux với useSelector và shallowEqual cho phép các component đăng ký chỉ các phần cụ thể của trạng thái, điều này giảm đáng kể số lần kết xuất lại trong các ứng dụng lớn. Context cũng có thể được tối ưu hóa bằng cách chia thành nhiều context nhỏ.
| Tiêu chí | useContext | Redux |
|---|---|---|
| Độ phức tạp | Không có phụ thuộc bên ngoài | Yêu cầu thiết lập store và middleware |
| Kết xuất lại | Tất cả người tiêu dùng khi có thay đổi | Chỉ những người đăng ký một slice cụ thể |
| DevTools | React DevTools | Redux DevTools với time-travel |
| Middleware | Không được hỗ trợ | Redux Thunk, Saga, Observable |
| Khi nào chọn | Ứng dụng vừa, 3–5 context | Ứng dụng lớn với logic kinh doanh phức tạp |
Theo Người bảo trì Redux — Khi nào sử dụng Redux (2024), 70% ứng dụng React không cần Redux. Nếu bạn có ít hơn 50 component và trạng thái không liên quan đến logic phức tạp với bộ nhớ đệm, debounce và hiệu ứng phụ — useContext + useReducer là quá đủ. Redux thêm mã khung và nên được sử dụng một cách có ý thức.
Lỗi phổ biến nhất là tạo lại đối tượng value ở mỗi lần kết xuất Provider. Nếu bạn truyền value={{ user, login }} cho Provider, một đối tượng mới được tạo ở mỗi lần kết xuất Provider, khiến tất cả người tiêu dùng kết xuất lại ngay cả khi dữ liệu không thay đổi. Giải pháp là ghi nhớ giá trị bằng useMemo hoặc sử dụng các context riêng biệt cho dữ liệu thay đổi thường xuyên và hiếm khi.
// ❌ Đối tượng mới ở mỗi lần kết xuất — tất cả người tiêu dùng kết xuất lại
<AuthContext.Provider value={{ user, login }}>{children}</AuthContext.Provider>
// ✅ Giá trị được ghi nhớ — chỉ kết xuất lại khi user hoặc login thay đổi
const authValue = useMemo(() => ({ user, login }), [user, login]);
<AuthContext.Provider value={authValue}>{children}</AuthContext.Provider>
Để giải quyết vấn đề “context lớn”, hãy chia trạng thái toàn cục thành các nhóm logic: AuthContext, ThemeContext, I18nContext. Mỗi context chịu trách nhiệm cho lĩnh vực riêng của nó và cập nhật độc lập. Điều này đơn giản hơn so với cố gắng tối ưu hóa một context khổng lồ thông qua useMemo và mang lại hành vi kết xuất lại có thể dự đoán được hơn.
Các câu hỏi thường gặp
Có, nếu bạn truyền một hàm thay đổi trong value của Provider. Mẫu điển hình là lưu trữ trạng thái trong Provider và truyền cả dữ liệu lẫn hàm cập nhật qua useContext. Các component con gọi các hàm này, và sự thay đổi trạng thái trong Provider tự động cập nhật tất cả người tiêu dùng. Đây là một sự thay thế cơ bản cho Redux trong các ứng dụng nhỏ.
Đối với các kịch bản đơn giản, useContext nhanh hơn do không có chi phí từ store và middleware. Nhưng với các cập nhật thường xuyên và nhiều người tiêu dùng, Redux chiến thắng vì các bộ chọn (useSelector) của nó đăng ký các phần cụ thể của trạng thái, trong khi useContext kết xuất lại tất cả người tiêu dùng khi có bất kỳ thay đổi nào. Đối với các ứng dụng có tần suất cập nhật cao (hoạt ảnh, thời gian thực), hãy chọn Redux hoặc các thư viện chuyên dụng.
Không. useContext, giống như tất cả các hook, chỉ có thể được gọi bên trong một component chức năng React hoặc hook tùy chỉnh. Nếu bạn cần lấy giá trị context trong một hàm thông thường (ví dụ, trong tiện ích hoặc dịch vụ), hãy truyền nó như một tham số từ component hoặc sử dụng một mô-đun riêng biệt với trạng thái toàn cục bên ngoài React.
Việc gõ kiểu cho context trong TypeScript được thực hiện bằng cách chỉ định kiểu trong createContext: createContext<AuthContextType | null>(null). Điều này đảm bảo rằng useContext(AuthContext) trả về giá trị đúng kiểu. Một mẫu thuận tiện là tạo một hook tùy chỉnh useAuth gọi useContext, kiểm tra null và ném ra lỗi rõ ràng: “useAuth phải được sử dụng trong AuthProvider”.
Lý do phổ biến nhất là component tiêu dùng không nằm bên trong Provider tương ứng. Kiểm tra rằng Provider bao bọc toàn bộ cây con nơi useContext được sử dụng. Lý do thứ hai — một đối tượng context khác đã được truyền cho Provider: nhà phát triển tạo context bằng cách gọi createContext nhưng sử dụng useContext với một phiên bản khác của createContext.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm