React Context API

Context API 提供了一种在组件树中共享数据的方式,避免了逐层传递props的繁琐过程。

什么是Context API?

Context API是React提供的一种组件间通信机制,允许你将数据通过组件树传递,而无需在每个层级手动传递props。

使用场景:
  • 主题切换(深色/浅色模式)
  • 用户认证信息共享
  • 多语言/国际化
  • 全局状态管理
  • 共享UI状态(如侧边栏开关)

基本用法

1. 创建Context

import React, { createContext, useState } from 'react';

// 创建Context对象
const ThemeContext = createContext();

const ThemeProvider = ({ children }) => {
  const [theme, setTheme] = useState('light');

  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

export { ThemeContext, ThemeProvider };

2. 在应用中提供Context

import React from 'react';
import { ThemeProvider } from './ThemeContext';
import AppContent from './AppContent';

function App() {
  return (
    <ThemeProvider>
      <AppContent />
    </ThemeProvider>
  );
}

export default App;

3. 在组件中使用Context(类组件)

import React, { Component } from 'react';
import { ThemeContext } from './ThemeContext';

class ThemedButton extends Component {
  static contextType = ThemeContext;

  render() {
    const { theme, toggleTheme } = this.context;

    return (
      <button
        onClick={toggleTheme}
        style={{
          backgroundColor: theme === 'light' ? '#fff' : '#333',
          color: theme === 'light' ? '#000' : '#fff'
        }}
      >
        切换主题
      </button>
    );
  }
}

export default ThemedButton;

4. 使用useContext Hook(函数组件)

import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function ThemeToggle() {
  const { theme, toggleTheme } = useContext(ThemeContext);

  return (
    <div className={`theme-toggle ${theme}`}>
      <span>当前主题: {theme}</span>
      <button onClick={toggleTheme}>
        切换到 {theme === 'light' ? '深色' : '浅色'} 模式
      </button>
    </div>
  );
}

export default ThemeToggle;

多个Context组合使用

import React, { createContext, useContext } from 'react';

// 创建多个Context
const UserContext = createContext();
const SettingsContext = createContext();

// 提供多个Context
function AppProviders({ children }) {
  const user = { name: 'John Doe', role: 'admin' };
  const settings = { language: 'zh-CN', notifications: true };

  return (
    <UserContext.Provider value={user}>
      <SettingsContext.Provider value={settings}>
        {children}
      </SettingsContext.Provider>
    </UserContext.Provider>
  );
}

// 在组件中使用多个Context
function UserProfile() {
  const user = useContext(UserContext);
  const settings = useContext(SettingsContext);

  return (
    <div>
      <h3>用户信息</h3>
      <p>姓名: {user.name}</p>
      <p>角色: {user.role}</p>
      <p>语言: {settings.language}</p>
      <p>通知: {settings.notifications ? '开启' : '关闭'}</p>
    </div>
  );
}

使用Consumer组件

除了useContext Hook,还可以使用Consumer组件(适用于React 16.3+):

import React from 'react';
import { ThemeContext } from './ThemeContext';

function ThemeDisplay() {
  return (
    <ThemeContext.Consumer>
      {({ theme }) => (
        <div className={`theme-display ${theme}`}>
          <h3>当前主题: {theme}</h3>
          <div className="theme-preview">
            {theme === 'light' ? '🌞' : '🌙'}
          </div>
        </div>
      )}
    </ThemeContext.Consumer>
  );
}
注意事项:
  • Context的value变化会导致所有使用该Context的组件重新渲染
  • 对于频繁更新的数据,考虑使用状态管理库(如Redux)
  • 避免创建过多Context,合理组织Context结构
  • 使用memo或useMemo优化性能

完整示例:用户认证Context

import React, { createContext, useState, useContext, useEffect } from 'react';

// 创建Context
const AuthContext = createContext();

// 创建Provider组件
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // 模拟从localStorage或API获取用户信息
    const savedUser = localStorage.getItem('user');
    if (savedUser) {
      setUser(JSON.parse(savedUser));
    }
    setLoading(false);
  }, []);

  const login = async (username, password) => {
    // 模拟登录API调用
    const mockUser = {
      id: 1,
      username,
      token: 'mock-jwt-token'
    };
    setUser(mockUser);
    localStorage.setItem('user', JSON.stringify(mockUser));
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };

  const value = {
    user,
    loading,
    login,
    logout,
    isAuthenticated: !!user
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义Hook
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth必须在AuthProvider内使用');
  }
  return context;
}

// 受保护的路由组件
export function ProtectedRoute({ children }) {
  const { isAuthenticated, loading } = useAuth();

  if (loading) {
    return <div>加载中...</div>;
  }

  if (!isAuthenticated) {
    return <div>请先登录</div>;
  }

  return children;
}

性能优化

使用memo防止不必要的重新渲染

import React, { memo, useContext } from 'react';
import { ThemeContext } from './ThemeContext';

const ExpensiveComponent = memo(function ExpensiveComponent() {
  const { theme } = useContext(ThemeContext);

  console.log('ExpensiveComponent渲染了');

  // 模拟昂贵的计算
  const expensiveResult = Array(1000)
    .fill(null)
    .map((_, i) => i)
    .reduce((a, b) => a + b, 0);

  return (
    <div className={theme}>
      计算结果: {expensiveResult}
    </div>
  );
});

export default ExpensiveComponent;

分离Context以减少重新渲染

// 不好的做法:所有数据在一个Context中
const AppContext = createContext();

// 好的做法:按功能分离Context
const ThemeContext = createContext();
const UserContext = createContext();
const NotificationContext = createContext();

// 组件只订阅需要的Context
function UserProfile() {
  const user = useContext(UserContext); // 只有user变化时才重新渲染

  return (
    <div>
      <h2>{user.name}</h2>
      <p>{user.email}</p>
    </div>
  );
}

Context vs Props vs Redux

方案 适用场景 优点 缺点
Props 父子组件简单通信 简单直观,类型检查 props drilling问题
Context API 中大型应用,组件树深层共享 React原生,无需额外依赖 频繁更新可能影响性能
Redux 复杂状态管理,需要时间旅行调试 强大工具集,成熟的生态 学习成本高,模板代码多

最佳实践

  1. 为每个Context创建单独的Provider组件
  2. 创建自定义Hook(如useAuth)简化使用
  3. 为Context提供默认值
  4. 合理拆分Context,避免单个Context过大
  5. 在性能敏感处使用memo和useMemo
  6. 提供TypeScript类型定义(如果使用TypeScript)
示例项目结构建议:
src/
├── contexts/
│   ├── ThemeContext.js
│   ├── AuthContext.js
│   └── index.js
├── hooks/
│   └── useAuth.js
└── components/