Context API
下面,我们来系统的梳理关于 React Context API 的基本知识点:
一、Context API 概述
1.1 什么是 Context API
Context API 是 React 提供的一种组件间通信机制,允许数据在组件树中直接传递,无需通过 props 逐层传递。它解决了 “prop drilling”(属性钻取)问题,特别适合全局数据的共享。
1.2 为什么需要 Context API
- 避免 prop drilling:减少中间组件传递不必要的 props
- 全局状态管理:主题、用户认证、语言偏好等全局数据
- 简化复杂组件树:跨层级组件通信更高效
- 替代 Redux 场景:中小型应用的状态管理方案
1.3 适用场景
- 主题切换(深色/浅色模式)
- 用户认证信息
- 多语言国际化
- 全局配置信息
- 跨组件状态共享
二、核心 API 详解
2.1 React.createContext
const MyContext = React.createContext(defaultValue);
- 创建 Context 对象
defaultValue
在未匹配到 Provider 时使用- 返回对象包含
Provider
和Consumer
组件
2.2 Context.Provider
<MyContext.Provider value={/* 共享的值 */}>{/* 子组件 */}
</MyContext.Provider>
- 提供数据给子组件树
value
属性是必需的,可以是任何类型- 当 value 变化时,所有消费者组件会重新渲染
2.3 Context.Consumer
<MyContext.Consumer>{value => /* 基于 context 值进行渲染 */}
</MyContext.Consumer>
- 类组件中订阅 Context 变更的方式
- 需要函数作为子元素(function as a child)
2.4 useContext Hook
const value = useContext(MyContext);
- 函数组件中访问 Context 值
- 返回当前 context 值
- 当 Provider 更新时,该 Hook 会触发重新渲染
三、基础用法
3.1 创建 Context
// ThemeContext.js
import React from 'react';const ThemeContext = React.createContext({theme: 'light',toggleTheme: () => {}
});export default ThemeContext;
3.2 提供 Context
// App.js
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import Toolbar from './Toolbar';function App() {const [theme, setTheme] = useState('light');const toggleTheme = () => {setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');};return (<ThemeContext.Provider value={{ theme, toggleTheme }}><Toolbar /></ThemeContext.Provider>);
}
3.3 消费 Context
函数组件方式
// ThemedButton.js
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';function ThemedButton() {const { theme, toggleTheme } = useContext(ThemeContext);return (<button onClick={toggleTheme}style={{backgroundColor: theme === 'dark' ? '#333' : '#CCC',color: theme === 'dark' ? '#FFF' : '#000'}}>切换主题</button>);
}
类组件方式
// ThemedHeader.js
import React from 'react';
import ThemeContext from './ThemeContext';class ThemedHeader extends React.Component {static contextType = ThemeContext;render() {const { theme } = this.context;return (<header style={{background: theme === 'dark' ? '#222' : '#EEE',color: theme === 'dark' ? '#FFF' : '#333'}}><h1>主题化标题</h1></header>);}
}
四、高级用法
4.1 多 Context 嵌套
// App.js
import React from 'react';
import ThemeContext from './ThemeContext';
import UserContext from './UserContext';
import Dashboard from './Dashboard';function App() {const [theme, setTheme] = React.useState('light');const [user, setUser] = React.useState(null);return (<ThemeContext.Provider value={{ theme, setTheme }}><UserContext.Provider value={{ user, setUser }}><Dashboard /></UserContext.Provider></ThemeContext.Provider>);
}
4.2 组合多个 Context
// useCombinedContext.js
import { useContext } from 'react';
import ThemeContext from './ThemeContext';
import UserContext from './UserContext';export default function useCombinedContext() {const theme = useContext(ThemeContext);const user = useContext(UserContext);return {...theme,...user};
}// Dashboard.js
import useCombinedContext from './useCombinedContext';function Dashboard() {const { theme, user, setTheme, setUser } = useCombinedContext();// 使用组合后的值...
}
4.3 Context + useReducer
// AppStateContext.js
import React, { useReducer } from 'react';const initialState = {cart: [],user: null,theme: 'light'
};function reducer(state, action) {switch (action.type) {case 'ADD_TO_CART':return { ...state, cart: [...state.cart, action.item] };case 'SET_USER':return { ...state, user: action.user };case 'TOGGLE_THEME':return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };default:return state;}
}export const AppStateContext = React.createContext();
export const AppDispatchContext = React.createContext();export function AppProvider({ children }) {const [state, dispatch] = useReducer(reducer, initialState);return (<AppStateContext.Provider value={state}><AppDispatchContext.Provider value={dispatch}>{children}</AppDispatchContext.Provider></AppStateContext.Provider>);
}
4.4 性能优化
使用 React.memo
const UserProfile = React.memo(({ user }) => {// 仅在 user 改变时重新渲染return <div>{user.name}</div>;
});
拆分 Context
// 将频繁变化的状态和稳定状态分离
const UserContext = React.createContext();
const CartContext = React.createContext();function App() {const [user, setUser] = useState(null);const [cart, setCart] = useState([]);return (<UserContext.Provider value={user}><CartContext.Provider value={{ cart, setCart }}>{/* 子组件 */}</CartContext.Provider></UserContext.Provider>);
}
使用 useMemo
function App() {const [user, setUser] = useState(null);const value = useMemo(() => ({ user, setUser }), [user]);return (<UserContext.Provider value={value}>{/* 子组件 */}</UserContext.Provider>);
}
五、实践案例
5.1 创建自定义 Provider
// AuthProvider.js
import React, { useState, createContext, useContext } from 'react';const AuthContext = createContext(null);export function AuthProvider({ children }) {const [user, setUser] = useState(null);const login = (userData) => {// 登录逻辑...setUser(userData);};const logout = () => {// 登出逻辑...setUser(null);};const value = { user, login, logout };return (<AuthContext.Provider value={value}>{children}</AuthContext.Provider>);
}export function useAuth() {const context = useContext(AuthContext);if (!context) {throw new Error('useAuth必须在AuthProvider内使用');}return context;
}
5.2 创建自定义 Hook
// useTheme.js
import { useContext } from 'react';
import ThemeContext from '../context/ThemeContext';export default function useTheme() {const context = useContext(ThemeContext);if (!context) {throw new Error('useTheme必须在ThemeProvider内使用');}return context;
}
5.3 文件组织规范
src/├── context/│ ├── AuthContext.js│ ├── ThemeContext.js│ └── CartContext.js├── hooks/│ ├── useAuth.js│ ├── useTheme.js│ └── useCart.js├── components/├── pages/└── App.js
5.4 类型安全 (TypeScript)
// ThemeContext.tsx
import React, { createContext, useContext, useState } from 'react';type Theme = 'light' | 'dark';interface ThemeContextType {theme: Theme;toggleTheme: () => void;
}const ThemeContext = createContext<ThemeContextType | undefined>(undefined);export const ThemeProvider: React.FC = ({ children }) => {const [theme, setTheme] = useState<Theme>('light');const toggleTheme = () => {setTheme(prev => prev === 'light' ? 'dark' : 'light');};return (<ThemeContext.Provider value={{ theme, toggleTheme }}>{children}</ThemeContext.Provider>);
};export const useTheme = (): ThemeContextType => {const context = useContext(ThemeContext);if (!context) {throw new Error('useTheme必须在ThemeProvider内使用');}return context;
};
六、常见问题与解决方案
6.1 Provider 值变化导致所有消费者重新渲染
解决方案:
function App() {const [state, setState] = useState({ count: 0, theme: 'light' });// 避免:每次渲染创建新对象// const value = { state, setState };// 正确:使用 useMemoconst value = useMemo(() => ({ state, setState }), [state]);return (<MyContext.Provider value={value}>{/* 子组件 */}</MyContext.Provider>);
}
6.2 未找到 Provider 错误
解决方案:创建自定义 Hook
function useCustomContext() {const context = useContext(MyContext);if (context === undefined) {throw new Error('useCustomContext 必须在 MyContext.Provider 内使用');}return context;
}
6.3 嵌套 Provider 覆盖问题
解决方案:明确 Provider 层级关系
function App() {return (<ThemeProvider><UserProvider><CartProvider>{/* 子组件 */}</CartProvider></UserProvider></ThemeProvider>);
}
6.4 Context 过度使用
解决方案:
- 仅对真正全局的数据使用 Context
- 局部状态使用 useState/useReducer
- 复杂应用考虑 Redux 或 Zustand
七、Context API 与 Redux 对比
特性 | Context API | Redux |
---|---|---|
学习曲线 | 简单 | 中等 |
设置复杂度 | 低 | 中到高 |
内置中间件 | 无 | Redux-Thunk, Saga 等 |
DevTools | 无 | 强大支持 |
性能优化 | 需要手动优化 | 自动优化 |
适用场景 | 中小应用、全局配置 | 大型应用、复杂状态 |
异步处理 | 需手动实现 | 内置支持 |
包大小 | React 内置 | 额外 2-5KB |
八、案例:电商应用状态管理
8.1 创建购物车 Context
// CartContext.js
import React, { createContext, useContext, useReducer } from 'react';const CartContext = createContext();const initialState = {items: [],total: 0,
};function cartReducer(state, action) {switch (action.type) {case 'ADD_ITEM':const existingItem = state.items.find(item => item.id === action.item.id);if (existingItem) {return {...state,items: state.items.map(item => item.id === action.item.id ? { ...item, quantity: item.quantity + 1 } : item),total: state.total + action.item.price};}return {...state,items: [...state.items, { ...action.item, quantity: 1 }],total: state.total + action.item.price};case 'REMOVE_ITEM':return {...state,items: state.items.filter(item => item.id !== action.id),total: state.total - state.items.find(item => item.id === action.id).price};case 'CLEAR_CART':return initialState;default:return state;}
}export function CartProvider({ children }) {const [state, dispatch] = useReducer(cartReducer, initialState);const addItem = (item) => {dispatch({ type: 'ADD_ITEM', item });};const removeItem = (id) => {dispatch({ type: 'REMOVE_ITEM', id });};const clearCart = () => {dispatch({ type: 'CLEAR_CART' });};const value = {items: state.items,total: state.total,addItem,removeItem,clearCart};return (<CartContext.Provider value={value}>{children}</CartContext.Provider>);
}export function useCart() {const context = useContext(CartContext);if (!context) {throw new Error('useCart必须在CartProvider内使用');}return context;
}
8.2 在组件中使用
// ProductItem.js
import React from 'react';
import { useCart } from './CartContext';function ProductItem({ product }) {const { addItem } = useCart();return (<div className="product"><h3>{product.name}</h3><p>¥{product.price.toFixed(2)}</p><button onClick={() => addItem(product)}>加入购物车</button></div>);
}// CartSummary.js
import React from 'react';
import { useCart } from './CartContext';function CartSummary() {const { items, total, clearCart } = useCart();return (<div className="cart-summary"><h2>购物车 ({items.length} 件商品)</h2><ul>{items.map(item => (<li key={item.id}>{item.name} × {item.quantity} - ¥{(item.price * item.quantity).toFixed(2)}</li>))}</ul><p>总计: ¥{total.toFixed(2)}</p><button onClick={clearCart}>清空购物车</button></div>);
}
九、总结与实践
9.1 Context API 核心要点
- 适用场景:全局状态管理,避免 prop drilling
- 核心组件:Provider 提供数据,Consumer/useContext 消费数据
- 性能优化:拆分 Context,使用 useMemo,React.memo
- 最佳实践:自定义 Provider 和 Hook,类型安全
- 结合 useReducer:处理复杂状态逻辑
9.2 最佳实践总结
- 创建自定义 Providers:封装 Context 逻辑
- 使用自定义 Hooks:简化消费,增加类型安全
- 拆分 Context:避免不必要的重渲染
- 类型安全:使用 TypeScript 定义 Context 类型
- 避免滥用:仅对全局数据使用 Context
- 性能优化:注意对象引用和渲染优化