React Context 的使用

8 分钟
74 阅读
React Context 的使用

React Context 使用详解

React Context 提供了一种在组件树中共享数据的方式,无需通过逐层手动传递 props,特别适合全局数据(如主题、用户认证等)的共享。

一、Context 基本概念

1. 什么是 Context?

  • 解决组件多层嵌套传递 props的问题
  • 实现跨组件层级的数据共享
  • 适合全局数据(如用户信息、UI主题、语言偏好等)

2. 使用场景

  • ✅ 主题切换(深色/浅色模式)
  • ✅ 用户认证信息
  • ✅ 本地化语言
  • ✅ 全局缓存数据
  • ❌ 组件间简单通信(优先考虑 props 或状态提升)

二、Context 核心 API

1. React.createContext

jsx 复制代码
const MyContext = React.createContext(defaultValue);
  • 创建一个 Context 对象
  • defaultValue 仅在组件在树中没有匹配的 Provider时使用

2. Context.Provider

jsx 复制代码
<MyContext.Provider value={/* 共享的值 */}>
  {/* 子组件 */}
</MyContext.Provider>
  • 允许消费组件订阅 context 的变化
  • 接收 value prop 传递给消费组件

3. Context.Consumer

jsx 复制代码
<MyContext.Consumer>
  {value => /* 基于 context 值进行渲染 */}
</MyContext.Consumer>
  • 类组件中订阅 context 的另一种方式

4. useContext Hook

jsx 复制代码
const value = useContext(MyContext);
  • 函数组件中订阅 context 的推荐方式

三、基础使用示例

1. 创建 Context

jsx 复制代码
// contexts/theme.js
import { createContext } from 'react';

export const ThemeContext = createContext('light'); // 默认值 light

2. 提供 Context

jsx 复制代码
// App.js
import { ThemeContext } from './contexts/theme';

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

3. 消费 Context(类组件)

jsx 复制代码
class ThemedButton extends React.Component {
  static contextType = ThemeContext; // 方式1:类组件专用
  
  render() {
    const { theme, setTheme } = this.context; // 获取 context
    return (
      <button 
        onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
        style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}
      >
        Toggle Theme
      </button>
    );
  }
}

// 或者使用 Consumer(适用于复杂逻辑)
function ThemedButton() {
  return (
    <ThemeContext.Consumer>
      {({ theme, setTheme }) => (
        <button 
          onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
          style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}
        >
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}

4. 消费 Context(函数组件)

jsx 复制代码
import { useContext } from 'react';
import { ThemeContext } from './contexts/theme';

function ThemedButton() {
  const { theme, setTheme } = useContext(ThemeContext);
  
  return (
    <button 
      onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
      style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}
    >
      Toggle Theme
    </button>
  );
}

四、高级用法

1. 多层嵌套 Context

jsx 复制代码
// 多个 Context 嵌套
<ThemeContext.Provider value={theme}>
  <UserContext.Provider value={user}>
    <Layout />
  </UserContext.Provider>
</ThemeContext.Provider>

// 消费多个 Context
function Content() {
  const theme = useContext(ThemeContext);
  const user = useContext(UserContext);
  
  return (
    <div style={{ color: theme === 'dark' ? '#fff' : '#000' }}>
      Welcome, {user.name}!
    </div>
  );
}

2. 动态 Context

jsx 复制代码
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);

  const contextValue = {
    theme,
    setTheme,
    user,
    login: (userData) => setUser(userData),
    logout: () => setUser(null)
  };

  return (
    <AppContext.Provider value={contextValue}>
      <Header />
      <MainContent />
    </AppContext.Provider>
  );
}

3. 自定义 Provider 组件

jsx 复制代码
// contexts/auth.js
export const AuthContext = createContext();

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>
  );
}

// App.js
import { AuthProvider } from './contexts/auth';

function App() {
  return (
    <AuthProvider>
      <Router>
        {/* 路由配置 */}
      </Router>
    </AuthProvider>
  );
}

4. 性能优化(避免不必要的渲染)

jsx 复制代码
// 使用 useMemo 和 useCallback 优化
function App() {
  const [theme, setTheme] = useState('light');
  
  // 避免每次渲染都创建新的对象
  const contextValue = useMemo(() => ({
    theme,
    toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light')
  }), [theme]);

  return (
    <ThemeContext.Provider value={contextValue}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

五、最佳实践

1. 组织 Context 文件

推荐目录结构:

复制代码
src/
  contexts/
    ThemeContext.js
    AuthContext.js
    LocaleContext.js
    index.js  // 统一导出

2. 分离业务逻辑

jsx 复制代码
// contexts/auth.js
export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
}

// 组件中使用
function UserProfile() {
  const { user } = useAuth();
  // ...
}

3. 类型安全(TypeScript)

typescript 复制代码
interface ThemeContextType {
  theme: 'light' | 'dark';
  setTheme: (theme: 'light' | 'dark') => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

function useTheme() {
  const context = useContext(ThemeContext);
  if (context === undefined) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

六、常见问题

1. Provider 未找到时使用默认值

jsx 复制代码
const MyContext = createContext('default');

function Component() {
  const value = useContext(MyContext); // 如果没有 Provider,value 为 'default'
  // ...
}

2. 避免滥用 Context

  • ❌ 不要用来替代所有组件通信
  • ✅ 只用于真正需要跨多层级的全局数据

3. 性能问题解决方案

jsx 复制代码
// 方案1:拆分 Context
// 不要把所有状态放在一个 Context 中
<UserContext.Provider value={user}>
  <ThemeContext.Provider value={theme}>
    {/* ... */}
  </ThemeContext.Provider>
</UserContext.Provider>

// 方案2:使用 useMemo 记忆化值
const contextValue = useMemo(() => ({ user, setUser }), [user]);

七、完整示例(主题切换)

1. 创建 Context

jsx 复制代码
// contexts/theme.js
import { createContext, useContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

2. 提供 Context

jsx 复制代码
// App.js
import { ThemeProvider } from './contexts/theme';

function App() {
  return (
    <ThemeProvider>
      <Header />
      <MainContent />
      <Footer />
    </ThemeProvider>
  );
}

3. 消费 Context

jsx 复制代码
// components/ThemeToggle.js
import { useTheme } from '../contexts/theme';

function ThemeToggle() {
  const { theme, toggleTheme } = useTheme();
  
  return (
    <button onClick={toggleTheme}>
      Current Theme: {theme} (Click to toggle)
    </button>
  );
}

总结

React Context 的核心使用流程:

  1. 创建 ContextcreateContext()
  2. 提供数据<Context.Provider value={value}>
  3. 消费数据
    • 类组件:static contextType<Context.Consumer>
    • 函数组件:useContext()

最佳实践:

  • 为不同的全局数据创建多个 Context
  • 提供自定义 Hook(如 useTheme)简化消费
  • 对动态 Context 值进行性能优化
  • 在 TypeScript 中确保类型安全

Context 是 React 强大的状态管理工具,合理使用可以大幅简化深层嵌套组件的数据传递问题。

评论

评论

发表评论