React Contxt詳解
React 的 Context API 是用于跨組件層級傳遞數據的解決方案,尤其適合解決「prop drilling」(多層組件手動傳遞 props)的問題。以下是關于 Context 的詳細解析:
文章目錄
- React Contxt詳解
- 一、Context 核心概念
- 二、基礎用法
- 1. 創建 Context
- 2. Provider 提供數據
- 3. 在函數式組件中消費數據
- **一、基礎用法**
- 1. 創建 Context
- 2. 使用 Provider 提供數據
- 3. 在函數式組件中消費數據
- **二、性能優化**
- 1. 避免無效渲染
- 2. 拆分 Context
- **三、動態更新 Context**
- 1. 更新 Context 的值
- 2. 在子組件中更新 Context
- **四、結合 useReducer 管理復雜狀態**
- 1. 創建 Context 和 Reducer
- 2. 在子組件中分別消費狀態和派發
- 五、使用場景
- 1. 主題切換(Theme) - 注釋版
- 2. 用戶認證信息 - 注釋版
- 3. 多語言國際化(i18n)- 注釋版
- 4. 全局狀態管理(購物車)- 注釋版
- 5. 復雜表單狀態 - 注釋版
一、Context 核心概念
- Context 對象:通過
React.createContext()
創建,包含Provider
和Consumer
兩個組件。 - Provider:提供數據的組件,包裹下游組件,通過
value
屬性傳遞數據。 - Consumer:消費數據的組件(或使用
useContext
Hook),通過訂閱 Context 獲取最新值。
二、基礎用法
1. 創建 Context
import React, { createContext, useContext, useState } from 'react';// 1. 創建 Context(可選默認值)
const ThemeContext = createContext('light'); // 默認值 'light'
2. Provider 提供數據
function App() {const [theme, setTheme] = React.useState('dark');return (<ThemeContext.Provider value={{ theme, setTheme }}><Toolbar /></ThemeContext.Provider>);
}
3. 在函數式組件中消費數據
function ThemedButton() {// 使用 useContext Hook 獲取數據const { theme, setTheme } = useContext(ThemeContext);return (<buttononClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}style={{ background: theme === 'dark' ? '#333' : '#fff' }}>當前主題: {theme}</button>);
}
在 React 函數式組件中使用 Context 主要通過 useContext
Hook 實現。以下是詳細步驟和示例:
一、基礎用法
1. 創建 Context
import React, { createContext, useContext, useState } from 'react';// 1. 創建 Context(可選默認值)
const ThemeContext = createContext('light'); // 默認值 'light'
2. 使用 Provider 提供數據
function App() {const [theme, setTheme] = useState('dark');return (// Provider 通過 value 傳遞數據<ThemeContext.Provider value={{ theme, setTheme }}><Toolbar /></ThemeContext.Provider>);
}
3. 在函數式組件中消費數據
function ThemedButton() {// 使用 useContext Hook 獲取數據const { theme, setTheme } = useContext(ThemeContext);return (<buttononClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}style={{ background: theme === 'dark' ? '#333' : '#fff' }}>當前主題: {theme}</button>);
}
二、性能優化
1. 避免無效渲染
如果 Provider
的 value
是對象,每次父組件渲染會生成新對象,導致子組件無效重渲染。使用 useMemo
優化:
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 中:
// UserContext(頻繁更新)
const UserContext = createContext({ name: 'Guest' });// ThemeContext(不常更新)
const ThemeContext = createContext('light');
三、動態更新 Context
1. 更新 Context 的值
通過 useState
或 useReducer
動態更新 Context:
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
function ThemeSwitcher() {const { toggleTheme } = useContext(ThemeContext);return <button onClick={toggleTheme}>切換主題</button>;
}
四、結合 useReducer 管理復雜狀態
1. 創建 Context 和 Reducer
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. 在子組件中分別消費狀態和派發
function ThemeSwitcher() {const state = useContext(StateContext);const dispatch = useContext(DispatchContext);return (<buttononClick={() => 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) - 注釋版
// 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. 用戶認證信息 - 注釋版
// 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)- 注釋版
// 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. 全局狀態管理(購物車)- 注釋版
// 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. 復雜表單狀態 - 注釋版
// 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><inputtype="text"value={formData.personalInfo.firstName}onChange={(e) => updateField('personalInfo', 'firstName', e.target.value)}placeholder="請輸入名字"/></div><div className="form-group"><label>姓氏:</label><inputtype="text"value={formData.personalInfo.lastName}onChange={(e) => updateField('personalInfo', 'lastName', e.target.value)}placeholder="請輸入姓氏"/></div><div className="form-group"><label>郵箱:</label><inputtype="email"value={formData.personalInfo.email}onChange={(e) => updateField('personalInfo', 'email', e.target.value)}placeholder="example@domain.com"/></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><inputtype="email"value={formData.personalInfo.email}onChange={(e) => updateField('personalInfo', 'email', e.target.value)}placeholder="example@domain.com"/></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>);
}