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="[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>
  );
}
相关推荐
开发者小天7 分钟前
React中startTransition的使用
前端·react.js·c#
Elastic 中国社区官方博客1 小时前
JavaScript 中使用 Elasticsearch 的正确方式,第一部分
大数据·开发语言·javascript·数据库·elasticsearch·搜索引擎·全文检索
万物得其道者成2 小时前
从零开始创建一个 Next.js 项目并实现一个 TodoList 示例
开发语言·javascript·ecmascript
ZHOU_WUYI2 小时前
使用Docker部署React应用与Nginx
nginx·react.js·docker
sunbyte3 小时前
50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | Expanding Cards (展开式卡片)
javascript·vue.js·ecmascript
xx24063 小时前
React Native简介
javascript·react native·react.js
重生之后端学习4 小时前
02-前端Web开发(JS+Vue+Ajax)
java·开发语言·前端·javascript·vue.js
布鲁斯的快乐小屋4 小时前
axios的基本使用
javascript·ajax
来自星星的坤7 小时前
【Vue 3 + Vue Router 4】如何正确重置路由实例(resetRouter)——避免“VueRouter is not defined”错误
前端·javascript·vue.js