React Contxt详解

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 核心概念

  1. Context 对象 :通过 React.createContext() 创建,包含 ProviderConsumer 两个组件。
  2. Provider :提供数据的组件,包裹下游组件,通过 value 属性传递数据。
  3. 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. 避免无效渲染

如果 Providervalue 是对象,每次父组件渲染会生成新对象,导致子组件无效重渲染。使用 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 的值

通过 useStateuseReducer 动态更新 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="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>
        <input
          type="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>
  );
}
相关推荐
柒儿吖3 分钟前
Electron for 鸿蒙PC - Native模块Mock与降级策略
javascript·electron·harmonyos
颜酱19 分钟前
package.json 配置指南
前端·javascript·node.js
珑墨40 分钟前
【唯一随机数】如何用JavaScript的Set生成唯一的随机数?
开发语言·前端·javascript·ecmascript
前端老宋Running2 小时前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔2 小时前
如何自己构建一个Markdown增量渲染器
前端·javascript
WILLF2 小时前
HTML iframe 标签
前端·javascript
ohyeah2 小时前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript
uup2 小时前
JavaScript 中 this 指向问题
javascript
4***14903 小时前
TypeScript在React中的前端框架
react.js·typescript·前端框架
小皮虾3 小时前
告别服务器!小程序纯前端“图片转 PDF”工具,隐私安全又高效
前端·javascript·微信小程序