React Contxt详解
React 的 Context API 是用于跨组件层级传递数据的解决方案,尤其适合解决「prop drilling」(多层组件手动传递 props)的问题。以下是关于 Context 的详细解析:
文章目录
- [React Contxt详解](#React Contxt详解)
-
-
- [一、Context 核心概念](#一、Context 核心概念)
- 二、基础用法
-
- [1. 创建 Context](#1. 创建 Context)
- [2. Provider 提供数据](#2. Provider 提供数据)
- [3. 在函数式组件中消费数据](#3. 在函数式组件中消费数据)
- **一、基础用法**
-
- [1. 创建 Context](#1. 创建 Context)
- [2. 使用 Provider 提供数据](#2. 使用 Provider 提供数据)
- [3. 在函数式组件中消费数据](#3. 在函数式组件中消费数据)
- **二、性能优化**
-
- [1. 避免无效渲染](#1. 避免无效渲染)
- [2. 拆分 Context](#2. 拆分 Context)
- [**三、动态更新 Context**](#三、动态更新 Context)
-
- [1. 更新 Context 的值](#1. 更新 Context 的值)
- [2. 在子组件中更新 Context](#2. 在子组件中更新 Context)
- [**四、结合 useReducer 管理复杂状态**](#四、结合 useReducer 管理复杂状态)
-
- [1. 创建 Context 和 Reducer](#1. 创建 Context 和 Reducer)
- [2. 在子组件中分别消费状态和派发](#2. 在子组件中分别消费状态和派发)
- 五、使用场景
-
- [1. 主题切换(Theme) - 注释版](#1. 主题切换(Theme) - 注释版)
- [2. 用户认证信息 - 注释版](#2. 用户认证信息 - 注释版)
- [3. 多语言国际化(i18n)- 注释版](#3. 多语言国际化(i18n)- 注释版)
- [4. 全局状态管理(购物车)- 注释版](#4. 全局状态管理(购物车)- 注释版)
- [5. 复杂表单状态 - 注释版](#5. 复杂表单状态 - 注释版)
-
一、Context 核心概念
- Context 对象 :通过
React.createContext()
创建,包含Provider
和Consumer
两个组件。 - Provider :提供数据的组件,包裹下游组件,通过
value
属性传递数据。 - Consumer :消费数据的组件(或使用
useContext
Hook),通过订阅 Context 获取最新值。
二、基础用法
1. 创建 Context
jsx
import React, { createContext, useContext, useState } from 'react';
// 1. 创建 Context(可选默认值)
const ThemeContext = createContext('light'); // 默认值 'light'
2. Provider 提供数据
jsx
function App() {
const [theme, setTheme] = React.useState('dark');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
3. 在函数式组件中消费数据
jsx
function ThemedButton() {
// 使用 useContext Hook 获取数据
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
style={{ background: theme === 'dark' ? '#333' : '#fff' }}
>
当前主题: {theme}
</button>
);
}
在 React 函数式组件中使用 Context 主要通过 useContext
Hook 实现。以下是详细步骤和示例:
一、基础用法
1. 创建 Context
jsx
import React, { createContext, useContext, useState } from 'react';
// 1. 创建 Context(可选默认值)
const ThemeContext = createContext('light'); // 默认值 'light'
2. 使用 Provider 提供数据
jsx
function App() {
const [theme, setTheme] = useState('dark');
return (
// Provider 通过 value 传递数据
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
3. 在函数式组件中消费数据
jsx
function ThemedButton() {
// 使用 useContext Hook 获取数据
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
style={{ background: theme === 'dark' ? '#333' : '#fff' }}
>
当前主题: {theme}
</button>
);
}
二、性能优化
1. 避免无效渲染
如果 Provider
的 value
是对象,每次父组件渲染会生成新对象,导致子组件无效重渲染。使用 useMemo
优化:
jsx
function App() {
const [theme, setTheme] = useState('dark');
// 使用 useMemo 避免重复创建对象
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return (
<ThemeContext.Provider value={value}>
<Toolbar />
</ThemeContext.Provider>
);
}
2. 拆分 Context
将频繁更新的数据与不常更新的数据拆分到不同的 Context 中:
jsx
// UserContext(频繁更新)
const UserContext = createContext({ name: 'Guest' });
// ThemeContext(不常更新)
const ThemeContext = createContext('light');
三、动态更新 Context
1. 更新 Context 的值
通过 useState
或 useReducer
动态更新 Context:
jsx
function App() {
const [theme, setTheme] = useState('dark');
const toggleTheme = () => {
setTheme(prev => prev === 'dark' ? 'light' : 'dark');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
2. 在子组件中更新 Context
jsx
function ThemeSwitcher() {
const { toggleTheme } = useContext(ThemeContext);
return <button onClick={toggleTheme}>切换主题</button>;
}
四、结合 useReducer 管理复杂状态
1. 创建 Context 和 Reducer
jsx
const StateContext = createContext();
const DispatchContext = createContext();
function reducer(state, action) {
switch (action.type) {
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'dark' ? 'light' : 'dark' };
default:
return state;
}
}
function App() {
const [state, dispatch] = useReducer(reducer, { theme: 'dark' });
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
<Toolbar />
</DispatchContext.Provider>
</StateContext.Provider>
);
}
2. 在子组件中分别消费状态和派发
jsx
function ThemeSwitcher() {
const state = useContext(StateContext);
const dispatch = useContext(DispatchContext);
return (
<button
onClick={() => dispatch({ type: 'TOGGLE_THEME' })}
style={{ background: state.theme === 'dark' ? '#333' : '#fff' }}
>
当前主题: {state.theme}
</button>
);
}
操作 | 代码示例 | 适用场景 |
---|---|---|
创建 Context | createContext(defaultValue) |
定义全局状态 |
提供数据 | <ThemeContext.Provider value> |
父组件向任意深度子组件传值 |
消费数据 | useContext(ThemeContext) |
函数组件中直接获取数据 |
动态更新 | value={``{ theme, setTheme }} |
需要响应式更新的场景 |
性能优化 | useMemo 优化 Provider 的 value |
避免无效重渲染 |
五、使用场景
1. 主题切换(Theme) - 注释版
jsx
// ThemeContext.js
import React, { createContext, useContext, useState } from 'react';
// 创建主题上下文,默认值为 'light'
const ThemeContext = createContext();
/**
* 主题提供者组件
* @param {Object} props - 组件属性
* @param {React.ReactNode} props.children - 子组件
*/
export function ThemeProvider({ children }) {
// 使用 useState 管理主题状态,初始值为 'light'
const [theme, setTheme] = useState('light');
// 切换主题的函数
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
// 通过 Provider 向下传递主题状态和切换函数
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{/* 根容器动态添加主题类名 */}
<div className={`app ${theme}`}>
{children}
</div>
</ThemeContext.Provider>
);
}
/**
* 自定义主题 Hook
* @returns {{theme: string, toggleTheme: function}} 主题对象
*/
export const useTheme = () => {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme 必须在 ThemeProvider 内使用');
}
return context;
};
// App.js
import { ThemeProvider } from './ThemeContext';
function App() {
return (
// 包裹应用根组件提供主题功能
<ThemeProvider>
<Header />
<Content />
</ThemeProvider>
);
}
// Header.js
import { useTheme } from './ThemeContext';
function Header() {
// 获取当前主题状态和切换函数
const { theme, toggleTheme } = useTheme();
return (
<header>
<h1>My App</h1>
{/* 主题切换按钮 */}
<button onClick={toggleTheme}>
当前主题:{theme === 'light' ? '🌞 明亮' : '🌙 暗黑'}
</button>
</header>
);
}
2. 用户认证信息 - 注释版
jsx
// AuthContext.js
import React, { createContext, useContext, useState } from 'react';
// 创建认证上下文
const AuthContext = createContext();
/**
* 认证提供者组件
* @param {Object} props - 组件属性
* @param {React.ReactNode} props.children - 子组件
*/
export function AuthProvider({ children }) {
// 用户信息状态
const [user, setUser] = useState(null);
// 认证状态
const [isAuthenticated, setIsAuthenticated] = useState(false);
// 登录方法
const login = (userData) => {
setUser(userData);
setIsAuthenticated(true);
};
// 退出方法
const logout = () => {
setUser(null);
setIsAuthenticated(false);
};
return (
<AuthContext.Provider value={{
user,
isAuthenticated,
login,
logout
}}>
{children}
</AuthContext.Provider>
);
}
/**
* 自定义认证 Hook
* @returns {{
* user: Object|null,
* isAuthenticated: boolean,
* login: function,
* logout: function
* }} 认证上下文对象
*/
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth 必须在 AuthProvider 内使用');
}
return context;
};
// ProtectedRoute.js
import { useAuth } from './AuthContext';
import { Navigate } from 'react-router-dom';
/**
* 保护路由组件
* @param {Object} props - 组件属性
* @param {React.ReactNode} props.children - 子路由
*/
function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
// 未认证时跳转到登录页
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
// UserProfile.js
import { useAuth } from './AuthContext';
function UserProfile() {
const { user, logout } = useAuth();
return (
<div className="user-profile">
<h2>👤 用户资料</h2>
{user ? (
<>
<p>📛 姓名:{user.name}</p>
<p>📧 邮箱:{user.email}</p>
<p>🎭 角色:{user.role}</p>
</>
) : (
<p>⚠️ 未获取到用户信息</p>
)}
<button onClick={logout}>🚪 退出登录</button>
</div>
);
}
3. 多语言国际化(i18n)- 注释版
jsx
// I18nContext.js
import React, { createContext, useContext, useState } from 'react';
// 翻译字典配置
const translations = {
en: {
greeting: 'Hello',
button: 'Click me',
welcome: 'Welcome to our app'
},
zh: {
greeting: '你好',
button: '点击我',
welcome: '欢迎使用我们的应用'
}
};
// 创建国际化上下文
const I18nContext = createContext();
/**
* 国际化提供者组件
* @param {Object} props - 组件属性
* @param {React.ReactNode} props.children - 子组件
*/
export function I18nProvider({ children }) {
// 当前语言状态,默认英文
const [locale, setLocale] = useState('en');
/**
* 翻译函数
* @param {string} key - 翻译键
* @returns {string} 翻译文本
*/
const t = (key) => translations[locale][key] || key;
return (
<I18nContext.Provider value={{
locale,
setLocale,
t
}}>
{children}
</I18nContext.Provider>
);
}
/**
* 自定义国际化 Hook
* @returns {{
* locale: string,
* setLocale: function,
* t: function
* }} 国际化上下文对象
*/
export const useI18n = () => {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useI18n 必须在 I18nProvider 内使用');
}
return context;
};
// LanguageSwitcher.js
import { useI18n } from './I18nContext';
function LanguageSwitcher() {
const { locale, setLocale } = useI18n();
return (
<div className="language-switcher">
<select
value={locale}
onChange={(e) => setLocale(e.target.value)}
>
<option value="en">🇺🇸 English</option>
<option value="zh">🇨🇳 中文</option>
</select>
</div>
);
}
// Greeting.js
import { useI18n } from './I18nContext';
function Greeting() {
const { t } = useI18n();
return (
<div className="greeting">
<h1>🎉 {t('welcome')}</h1>
<button className="cta-button">
{t('button')}
</button>
</div>
);
}
4. 全局状态管理(购物车)- 注释版
jsx
// CartContext.js
import React, { createContext, useContext, useReducer } from 'react';
// 创建购物车上下文
const CartContext = createContext();
/**
* 购物车 reducer 处理函数
* @param {Object} state - 当前状态
* @param {Object} action - 操作对象
*/
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
// 检查是否已存在相同商品
const existingItem = state.items.find(
item => item.id === action.payload.id
);
if (existingItem) {
// 数量增加
return {
...state,
items: state.items.map(item =>
item.id === action.payload.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
// 新增商品
return {
...state,
items: [...state.items, { ...action.payload, quantity: 1 }]
};
case 'REMOVE_ITEM':
// 过滤移除商品
return {
...state,
items: state.items.filter(
item => item.id !== action.payload.id
)
};
case 'CLEAR_CART':
// 清空购物车
return { ...state, items: [] };
default:
return state;
}
};
/**
* 购物车提供者组件
* @param {Object} props - 组件属性
* @param {React.ReactNode} props.children - 子组件
*/
export function CartProvider({ children }) {
// 使用 useReducer 管理复杂状态
const [cart, dispatch] = useReducer(cartReducer, { items: [] });
// 添加商品到购物车
const addToCart = (product) => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
// 从购物车移除商品
const removeFromCart = (productId) => {
dispatch({ type: 'REMOVE_ITEM', payload: { id: productId } });
};
// 清空购物车
const clearCart = () => {
dispatch({ type: 'CLEAR_CART' });
};
// 计算总数量
const totalItems = cart.items.reduce(
(sum, item) => sum + item.quantity, 0
);
// 计算总金额
const totalPrice = cart.items.reduce(
(sum, item) => sum + (item.price * item.quantity), 0
);
return (
<CartContext.Provider value={{
cart,
addToCart,
removeFromCart,
clearCart,
totalItems,
totalPrice
}}>
{children}
</CartContext.Provider>
);
}
/**
* 自定义购物车 Hook
* @returns {{
* cart: Object,
* addToCart: function,
* removeFromCart: function,
* clearCart: function,
* totalItems: number,
* totalPrice: number
* }} 购物车上下文对象
*/
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error('useCart 必须在 CartProvider 内使用');
}
return context;
};
// ProductItem.js
import { useCart } from './CartContext';
function ProductItem({ product }) {
const { addToCart } = useCart();
return (
<div className="product-card">
<h3>{product.name}</h3>
<p>💰 价格:${product.price}</p>
<button
onClick={() => addToCart(product)}
className="add-to-cart"
>
🛒 加入购物车
</button>
</div>
);
}
// CartSummary.js
import { useCart } from './CartContext';
function CartSummary() {
const {
cart,
removeFromCart,
clearCart,
totalItems,
totalPrice
} = useCart();
return (
<div className="cart-summary">
<h2>🛍️ 购物车({totalItems} 件商品)</h2>
<ul className="cart-items">
{cart.items.map(item => (
<li key={item.id} className="cart-item">
<span>{item.name} × {item.quantity}</span>
<span>${item.price * item.quantity}</span>
<button
onClick={() => removeFromCart(item.id)}
className="remove-button"
>
❌ 移除
</button>
</li>
))}
</ul>
<div className="cart-total">
<p>💵 总计:${totalPrice}</p>
<button
onClick={clearCart}
className="clear-button"
>
🧹 清空购物车
</button>
</div>
</div>
);
}
5. 复杂表单状态 - 注释版
jsx
// FormContext.js
import React, { createContext, useContext, useState } from 'react';
// 创建表单上下文
const FormContext = createContext();
/**
* 表单提供者组件
* @param {Object} props - 组件属性
* @param {React.ReactNode} props.children - 子组件
*/
export function FormProvider({ children }) {
// 管理复杂表单数据结构
const [formData, setFormData] = useState({
personalInfo: {
firstName: '',
lastName: '',
email: ''
},
address: {
street: '',
city: '',
zipCode: ''
},
preferences: {
newsletter: false,
notifications: true
}
});
/**
* 更新表单字段
* @param {string} section - 表单区块(personalInfo/address/preferences)
* @param {string} field - 字段名称
* @param {any} value - 字段值
*/
const updateField = (section, field, value) => {
setFormData(prev => ({
...prev,
[section]: {
...prev[section],
[field]: value
}
}));
};
// 表单验证方法
const validateForm = () => {
// 示例验证逻辑
const isValid = !!(
formData.personalInfo.firstName &&
formData.personalInfo.email.includes('@')
);
return isValid;
};
return (
<FormContext.Provider value={{
formData,
updateField,
validateForm
}}>
{children}
</FormContext.Provider>
);
}
/**
* 自定义表单 Hook
* @returns {{
* formData: Object,
* updateField: function,
* validateForm: function
* }} 表单上下文对象
*/
export const useForm = () => {
const context = useContext(FormContext);
if (!context) {
throw new Error('useForm 必须在 FormProvider 内使用');
}
return context;
};
// PersonalInfoStep.js
import { useForm } from './FormContext';
function PersonalInfoStep() {
const { formData, updateField } = useForm();
return (
<div className="form-section">
<h2>📝 个人信息</h2>
<div className="form-group">
<label>名字:</label>
<input
type="text"
value={formData.personalInfo.firstName}
onChange={(e) =>
updateField('personalInfo', 'firstName', e.target.value)
}
placeholder="请输入名字"
/>
</div>
<div className="form-group">
<label>姓氏:</label>
<input
type="text"
value={formData.personalInfo.lastName}
onChange={(e) =>
updateField('personalInfo', 'lastName', e.target.value)
}
placeholder="请输入姓氏"
/>
</div>
<div className="form-group">
<label>邮箱:</label>
<input
type="email"
value={formData.personalInfo.email}
onChange={(e) =>
updateField('personalInfo', 'email', e.target.value)
}
placeholder="[email protected]"
/>
</div>
</div>
);
}
// FormSubmit.js
import { useForm } from './FormContext';
function FormSubmit() {
const { formData, validateForm } = useForm();
const handleSubmit = () => {
if (validateForm()) {
console.log('✅ 表单验证通过,提交数据:', formData);
// 这里可以添加实际的提交逻辑
alert('表单提交成功!');
} else {
console.log('❌ 表单验证失败');
alert('请填写必填字段!');
}
};
return (
<button
onClick={handleSubmit}
className="submit-button"
>提交表单
</button>
);
}
</div>
<div className="form-group">
<label>邮箱:</label>
<input
type="email"
value={formData.personalInfo.email}
onChange={(e) =>
updateField('personalInfo', 'email', e.target.value)
}
placeholder="[email protected]"
/>
</div>
</div>
);
}
// FormSubmit.js
import { useForm } from './FormContext';
function FormSubmit() {
const { formData, validateForm } = useForm();
const handleSubmit = () => {
if (validateForm()) {
console.log('✅ 表单验证通过,提交数据:', formData);
// 这里可以添加实际的提交逻辑
alert('表单提交成功!');
} else {
console.log('❌ 表单验证失败');
alert('请填写必填字段!');
}
};
return (
<button
onClick={handleSubmit}
className="submit-button"
>提交表单
</button>
);
}