Skip to content

状态管理:Context API

React Native 进阶

Context API 是 React 内置的状态管理方案,它提供了一种在组件树中共享状态的方式,而不需要通过 props 逐层传递。在 React Native 开发中,Context API 是一种轻量级的状态管理解决方案,适合管理应用的全局状态,如用户信息、主题设置等。本文将详细介绍如何在 React Native 中使用 Context API 进行状态管理。

1. 基本概念

什么是 Context API?

Context API 是 React 提供的一种机制,用于在组件树中共享状态,而不需要通过 props 逐层传递。它由以下几个部分组成:

  • createContext:创建一个 Context 对象
  • Provider:提供状态的组件,包裹需要使用状态的组件
  • Consumer:消费状态的组件,使用 Provider 提供的状态

什么时候使用 Context API?

Context API 适合管理以下类型的状态:

  • 全局状态,如用户信息、主题设置、语言偏好等
  • 跨多个组件的状态,如应用的认证状态
  • 不需要复杂逻辑的状态管理

对于复杂的状态管理,如需要处理异步操作、状态依赖等,推荐使用 Redux 或 MobX 等专业的状态管理库。

2. 基本用法

创建 Context

首先,创建一个 Context 文件,定义状态和操作方法。

jsx
// context/AppContext.js
import React, { createContext, useState, useContext } from 'react';

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

// 创建 Provider 组件
export const AppProvider = ({ children }) => {
  // 定义状态
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  // 定义操作方法
  const login = (userData) => {
    setUser(userData);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  const toggleTheme = () => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  };
  
  // 提供状态和方法
  const value = {
    user,
    theme,
    login,
    logout,
    toggleTheme
  };
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
};

// 创建自定义 Hook,方便使用 Context
export const useApp = () => {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useApp must be used within an AppProvider');
  }
  return context;
};

export default AppContext;

使用 Provider

在应用的根组件中使用 Provider 包裹所有需要使用状态的组件。

jsx
// App.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { AppProvider } from './context/AppContext';
import HomeScreen from './screens/HomeScreen';
import ProfileScreen from './screens/ProfileScreen';

export default function App() {
  return (
    <AppProvider>
      <View style={styles.container}>
        <HomeScreen />
        <ProfileScreen />
      </View>
    </AppProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
});

使用 Context

在组件中使用自定义 Hook 来访问 Context 中的状态和方法。

jsx
// screens/HomeScreen.js
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useApp } from '../context/AppContext';

export default function HomeScreen() {
  const { user, theme, toggleTheme, login } = useApp();
  
  const handleLogin = () => {
    login({
      id: 1,
      name: '张三',
      email: 'zhangsan@example.com'
    });
  };
  
  return (
    <View style={[styles.container, theme === 'dark' && styles.darkContainer]}>
      <Text style={[styles.title, theme === 'dark' && styles.darkText]}>首页</Text>
      
      {user ? (
        <Text style={[styles.text, theme === 'dark' && styles.darkText]}>
          欢迎,{user.name}!
        </Text>
      ) : (
        <TouchableOpacity style={styles.button} onPress={handleLogin}>
          <Text style={styles.buttonText}>登录</Text>
        </TouchableOpacity>
      )}
      
      <TouchableOpacity style={styles.button} onPress={toggleTheme}>
        <Text style={styles.buttonText}>
          切换到{theme === 'light' ? '深色' : '浅色'}主题
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  darkContainer: {
    backgroundColor: '#333',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 18,
    marginBottom: 20,
  },
  darkText: {
    color: '#fff',
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
});
jsx
// screens/ProfileScreen.js
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useApp } from '../context/AppContext';

export default function ProfileScreen() {
  const { user, theme, logout, toggleTheme } = useApp();
  
  return (
    <View style={[styles.container, theme === 'dark' && styles.darkContainer]}>
      <Text style={[styles.title, theme === 'dark' && styles.darkText]}>个人中心</Text>
      
      {user ? (
        <View style={styles.userInfo}>
          <Text style={[styles.text, theme === 'dark' && styles.darkText]}>
            姓名:{user.name}
          </Text>
          <Text style={[styles.text, theme === 'dark' && styles.darkText]}>
            邮箱:{user.email}
          </Text>
          <TouchableOpacity style={[styles.button, styles.logoutButton]} onPress={logout}>
            <Text style={styles.buttonText}>退出登录</Text>
          </TouchableOpacity>
        </View>
      ) : (
        <Text style={[styles.text, theme === 'dark' && styles.darkText]}>
          请先登录
        </Text>
      )}
      
      <TouchableOpacity style={styles.button} onPress={toggleTheme}>
        <Text style={styles.buttonText}>
          切换到{theme === 'light' ? '深色' : '浅色'}主题
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  darkContainer: {
    backgroundColor: '#333',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 18,
    marginBottom: 12,
  },
  darkText: {
    color: '#fff',
  },
  userInfo: {
    marginBottom: 20,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12,
  },
  logoutButton: {
    backgroundColor: '#f44336',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
});

3. 高级用法

多个 Context

对于复杂的应用,可以创建多个 Context 来管理不同类型的状态。

jsx
// context/UserContext.js
import React, { createContext, useState, useContext } from 'react';

const UserContext = createContext();

export const UserProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  const login = (userData) => {
    setUser(userData);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  const value = {
    user,
    login,
    logout
  };
  
  return (
    <UserContext.Provider value={value}>
      {children}
    </UserContext.Provider>
  );
};

export const useUser = () => {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
};

export default UserContext;
jsx
// context/ThemeContext.js
import React, { createContext, useState, useContext } from 'react';

const ThemeContext = createContext();

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

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

export default ThemeContext;
jsx
// App.js
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { UserProvider } from './context/UserContext';
import { ThemeProvider } from './context/ThemeContext';
import HomeScreen from './screens/HomeScreen';
import ProfileScreen from './screens/ProfileScreen';

export default function App() {
  return (
    <UserProvider>
      <ThemeProvider>
        <View style={styles.container}>
          <HomeScreen />
          <ProfileScreen />
        </View>
      </ThemeProvider>
    </UserProvider>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
});
jsx
// screens/HomeScreen.js
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useUser } from '../context/UserContext';
import { useTheme } from '../context/ThemeContext';

export default function HomeScreen() {
  const { user, login } = useUser();
  const { theme, toggleTheme } = useTheme();
  
  const handleLogin = () => {
    login({
      id: 1,
      name: '张三',
      email: 'zhangsan@example.com'
    });
  };
  
  return (
    <View style={[styles.container, theme === 'dark' && styles.darkContainer]}>
      <Text style={[styles.title, theme === 'dark' && styles.darkText]}>首页</Text>
      
      {user ? (
        <Text style={[styles.text, theme === 'dark' && styles.darkText]}>
          欢迎,{user.name}!
        </Text>
      ) : (
        <TouchableOpacity style={styles.button} onPress={handleLogin}>
          <Text style={styles.buttonText}>登录</Text>
        </TouchableOpacity>
      )}
      
      <TouchableOpacity style={styles.button} onPress={toggleTheme}>
        <Text style={styles.buttonText}>
          切换到{theme === 'light' ? '深色' : '浅色'}主题
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  darkContainer: {
    backgroundColor: '#333',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  text: {
    fontSize: 18,
    marginBottom: 20,
  },
  darkText: {
    color: '#fff',
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 12,
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '500',
  },
});

Context 与 useReducer

对于复杂的状态逻辑,可以结合 useReducer 来使用 Context API。

jsx
// context/CounterContext.js
import React, { createContext, useReducer, useContext } from 'react';

// 定义 action 类型
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
const RESET = 'RESET';

// 初始状态
const initialState = {
  count: 0
};

// Reducer 函数
const counterReducer = (state, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    case RESET:
      return initialState;
    default:
      return state;
  }
};

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

// 创建 Provider 组件
export const CounterProvider = ({ children }) => {
  const [state, dispatch] = useReducer(counterReducer, initialState);
  
  // 定义操作方法
  const increment = () => {
    dispatch({ type: INCREMENT });
  };
  
  const decrement = () => {
    dispatch({ type: DECREMENT });
  };
  
  const reset = () => {
    dispatch({ type: RESET });
  };
  
  // 提供状态和方法
  const value = {
    count: state.count,
    increment,
    decrement,
    reset
  };
  
  return (
    <CounterContext.Provider value={value}>
      {children}
    </CounterContext.Provider>
  );
};

// 创建自定义 Hook
export const useCounter = () => {
  const context = useContext(CounterContext);
  if (!context) {
    throw new Error('useCounter must be used within a CounterProvider');
  }
  return context;
};

export default CounterContext;
jsx
// screens/CounterScreen.js
import React from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import { useCounter } from '../context/CounterContext';

export default function CounterScreen() {
  const { count, increment, decrement, reset } = useCounter();
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>计数器</Text>
      <Text style={styles.count}>{count}</Text>
      
      <View style={styles.buttonContainer}>
        <TouchableOpacity style={styles.button} onPress={decrement}>
          <Text style={styles.buttonText}>-</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.button} onPress={reset}>
          <Text style={styles.buttonText}>重置</Text>
        </TouchableOpacity>
        
        <TouchableOpacity style={styles.button} onPress={increment}>
          <Text style={styles.buttonText}>+</Text>
        </TouchableOpacity>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  count: {
    fontSize: 48,
    fontWeight: 'bold',
    marginBottom: 40,
  },
  buttonContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    width: '100%',
    maxWidth: 300,
  },
  button: {
    backgroundColor: '#4CAF50',
    padding: 16,
    borderRadius: 8,
    alignItems: 'center',
    flex: 1,
    marginHorizontal: 8,
  },
  buttonText: {
    color: '#fff',
    fontSize: 18,
    fontWeight: '500',
  },
});

Context 与 useCallback/useMemo

为了优化性能,可以使用 useCallback 和 useMemo 来缓存 Context 中的函数和计算值。

jsx
// context/AppContext.js
import React, { createContext, useState, useContext, useCallback, useMemo } from 'react';

const AppContext = createContext();

export const AppProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  // 使用 useCallback 缓存函数
  const login = useCallback((userData) => {
    setUser(userData);
  }, []);
  
  const logout = useCallback(() => {
    setUser(null);
  }, []);
  
  const toggleTheme = useCallback(() => {
    setTheme(prevTheme => prevTheme === 'light' ? 'dark' : 'light');
  }, []);
  
  // 使用 useMemo 缓存计算值
  const isLoggedIn = useMemo(() => !!user, [user]);
  
  const value = {
    user,
    theme,
    isLoggedIn,
    login,
    logout,
    toggleTheme
  };
  
  return (
    <AppContext.Provider value={value}>
      {children}
    </AppContext.Provider>
  );
};

export const useApp = () => {
  const context = useContext(AppContext);
  if (!context) {
    throw new Error('useApp must be used within an AppProvider');
  }
  return context;
};

export default AppContext;

4. 最佳实践

1. 合理划分 Context

根据功能和职责划分不同的 Context,避免将所有状态都放在一个 Context 中。

2. 使用自定义 Hook

创建自定义 Hook 来简化 Context 的使用,提高代码的可读性和可维护性。

3. 优化性能

  • 使用 useCallback 缓存函数,避免不必要的重渲染
  • 使用 useMemo 缓存计算值,减少重复计算
  • 只在必要时更新状态,避免过度渲染

4. 错误处理

在自定义 Hook 中添加错误处理,确保 Context 在正确的范围内使用。

5. 类型安全

如果使用 TypeScript,可以为 Context 添加类型定义,提高代码的类型安全性。

5. 常见问题与解决方案

问题 1:Context 不更新

问题:修改 Context 中的状态后,组件没有重新渲染。

解决方案

  • 确保使用 useState 或 useReducer 来管理状态
  • 确保在 Provider 中正确传递 value
  • 检查组件是否在 Provider 的范围内

问题 2:Context 导致过度渲染

问题:Context 中的状态更新导致所有使用该 Context 的组件重新渲染。

解决方案

  • 使用 useCallback 和 useMemo 优化性能
  • 拆分 Context,将不相关的状态放在不同的 Context 中
  • 使用 React.memo 包装组件,避免不必要的重渲染

问题 3:Context 初始化问题

问题:Context 的初始值与 Provider 提供的值不一致。

解决方案

  • 确保 Provider 在应用的根组件中使用
  • 检查 Context 的初始值设置
  • 使用懒加载或条件渲染来处理异步初始化

问题 4:多层 Context 嵌套

问题:多个 Context 嵌套导致代码难以维护。

解决方案

  • 使用组合模式,创建一个根 Provider 来管理所有 Context
  • 使用 useContext 组合多个 Context 的值
  • 考虑使用状态管理库如 Redux 来处理复杂的状态管理

6. 总结

Context API 是 React Native 中一种轻量级的状态管理解决方案,它提供了一种在组件树中共享状态的方式,而不需要通过 props 逐层传递。通过本文的学习,你应该掌握了以下内容:

  1. Context API 的基本概念和使用方法
  2. 创建和使用 Context Provider
  3. 使用自定义 Hook 简化 Context 的使用
  4. 结合 useReducer 处理复杂的状态逻辑
  5. 优化 Context 的性能
  6. 常见问题的解决方案

在实际开发中,合理使用 Context API 可以简化状态管理,提高代码的可维护性。对于简单的全局状态管理,Context API 是一个很好的选择;对于复杂的状态管理,可能需要使用更专业的状态管理库如 Redux 或 MobX。

© 2026 编程马·菜鸟教程 版权所有