ReactContext模式Skill react-context-patterns

React Context模式是一种用于在React应用中共享状态和管理全局数据的技术,有效避免prop drilling,提升组件复用性和应用性能。适用于前端开发中的状态管理、认证、主题切换、通知系统、模态框管理、表单处理等场景。关键词:React, Context, 状态管理, 前端开发, 组件通信, 性能优化, TypeScript, 全局状态, prop drilling避免。

前端开发 0 次安装 0 次浏览 更新于 3/25/2026

名称: react-context-patterns 用户可调用: false 描述: 在React Context模式用于状态管理时使用。当需要在组件树之间共享状态而避免prop drilling时使用。 允许工具:

  • Bash
  • Read

React Context模式

掌握React Context模式,以行业最佳实践构建高性能、可扩展的React应用。

理解Prop Drilling与Context

Prop drilling发生在您将props传递通过多个不需要它们的组件层时,仅仅为了到达深层嵌套的组件。

// Prop Drilling (避免)
function App() {
  const [user, setUser] = useState<User | null>(null);
  return <Layout user={user} setUser={setUser} />;
}

function Layout({ user, setUser }: Props) {
  // Layout不使用user,只是传递下去
  return <Sidebar user={user} setUser={setUser} />;
}

function Sidebar({ user, setUser }: Props) {
  // Sidebar不使用user,只是传递下去
  return <UserMenu user={user} setUser={setUser} />;
}

function UserMenu({ user, setUser }: Props) {
  // 最终在这里使用
  return <div>{user?.name}</div>;
}

Context通过提供一种在组件之间共享值的方式来解决这个问题,而无需显式通过每一层传递props:

// 使用Context (更好)
const UserContext = createContext<UserContextType | undefined>(undefined);

function App() {
  const [user, setUser] = useState<User | null>(null);
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Layout />
    </UserContext.Provider>
  );
}

function Layout() {
  return <Sidebar />; // 不需要props
}

function Sidebar() {
  return <UserMenu />; // 不需要props
}

function UserMenu() {
  const { user } = useContext(UserContext);
  return <div>{user?.name}</div>;
}

使用TypeScript创建和使用Context

import { createContext, useContext, useState, ReactNode } from 'react';

interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
  isLoading: boolean;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [isLoading, setIsLoading] = useState(false);

  const login = async (email: string, password: string) => {
    setIsLoading(true);
    try {
      const user = await api.login(email, password);
      setUser(user);
    } catch (error) {
      console.error('登录失败:', error);
      throw error;
    } finally {
      setIsLoading(false);
    }
  };

  const logout = () => {
    setUser(null);
    api.clearSession();
  };

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

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

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

使用useReducer管理复杂状态的Context

import { createContext, useContext, useReducer, ReactNode } from 'react';

interface CartItem {
  id: string;
  name: string;
  price: number;
  quantity: number;
}

interface State {
  items: CartItem[];
  total: number;
}

type Action =
  | { type: 'ADD_ITEM'; payload: CartItem }
  | { type: 'REMOVE_ITEM'; payload: string }
  | { type: 'UPDATE_QUANTITY'; payload: { id: string; quantity: number } }
  | { type: 'CLEAR_CART' };

const CartContext = createContext<{
  state: State;
  dispatch: React.Dispatch<Action>;
} | undefined>(undefined);

function cartReducer(state: State, action: Action): State {
  switch (action.type) {
    case 'ADD_ITEM': {
      const existingItem = state.items.find(i => i.id === action.payload.id);
      if (existingItem) {
        return {
          items: state.items.map(i =>
            i.id === action.payload.id
              ? { ...i, quantity: i.quantity + action.payload.quantity }
              : i
          ),
          total: state.total + action.payload.price * action.payload.quantity
        };
      }
      return {
        items: [...state.items, action.payload],
        total: state.total + action.payload.price * action.payload.quantity
      };
    }
    case 'REMOVE_ITEM': {
      const item = state.items.find(i => i.id === action.payload);
      return {
        items: state.items.filter(i => i.id !== action.payload),
        total: state.total - (item ? item.price * item.quantity : 0)
      };
    }
    case 'UPDATE_QUANTITY': {
      const item = state.items.find(i => i.id === action.payload.id);
      if (!item) return state;
      const priceDiff = item.price * (action.payload.quantity - item.quantity);
      return {
        items: state.items.map(i =>
          i.id === action.payload.id
            ? { ...i, quantity: action.payload.quantity }
            : i
        ),
        total: state.total + priceDiff
      };
    }
    case 'CLEAR_CART':
      return { items: [], total: 0 };
    default:
      return state;
  }
}

export function CartProvider({ children }: { children: ReactNode }) {
  const [state, dispatch] = useReducer(cartReducer, {
    items: [],
    total: 0
  });

  return (
    <CartContext.Provider value={{ state, dispatch }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  const context = useContext(CartContext);
  if (!context) throw new Error('useCart必须在CartProvider内使用');
  return context;
}

// 辅助hook,包含操作
export function useCartActions() {
  const { dispatch } = useCart();

  return {
    addItem: (item: CartItem) => dispatch({ type: 'ADD_ITEM', payload: item }),
    removeItem: (id: string) => dispatch({ type: 'REMOVE_ITEM', payload: id }),
    updateQuantity: (id: string, quantity: number) =>
      dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } }),
    clearCart: () => dispatch({ type: 'CLEAR_CART' })
  };
}

多个Context组合

import { ReactNode } from 'react';

// 组合多个providers
export function AppProviders({ children }: { children: ReactNode }) {
  return (
    <AuthProvider>
      <ThemeProvider>
        <CartProvider>
          <NotificationProvider>
            {children}
          </NotificationProvider>
        </CartProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

// 在主应用中使用
function App() {
  return (
    <AppProviders>
      <Router />
    </AppProviders>
  );
}

性能优化:拆分Context

拆分读写操作,防止不必要的重新渲染:

import { createContext, useContext, useState, ReactNode, useMemo } from 'react';

// 分离读写contexts
const UserStateContext = createContext<User | null>(null);
const UserDispatchContext = createContext<{
  setUser: (user: User | null) => void;
} | undefined>(undefined);

export function UserProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  // 使用useMemo防止重新渲染
  const dispatch = useMemo(() => ({ setUser }), []);

  return (
    <UserStateContext.Provider value={user}>
      <UserDispatchContext.Provider value={dispatch}>
        {children}
      </UserDispatchContext.Provider>
    </UserStateContext.Provider>
  );
}

// 组件只在使用的状态变化时重新渲染
export function useUser() {
  const context = useContext(UserStateContext);
  return context; // 可以为null
}

export function useUserDispatch() {
  const context = useContext(UserDispatchContext);
  if (!context) {
    throw new Error('useUserDispatch必须在UserProvider内使用');
  }
  return context;
}

使用useMemo保持值稳定的Context

import { createContext, useContext, useState, ReactNode, useMemo } from 'react';

interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
  primaryColor: string;
  secondaryColor: string;
}

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

export function ThemeProvider({ children }: { children: ReactNode }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');

  // 使用useMemo防止不必要的重新渲染
  const value = useMemo(() => ({
    theme,
    toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light'),
    primaryColor: theme === 'light' ? '#000000' : '#ffffff',
    secondaryColor: theme === 'light' ? '#666666' : '#cccccc'
  }), [theme]);

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

export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error('useTheme必须在ThemeProvider内使用');
  return context;
}

带有本地存储持久化的Context

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

interface Settings {
  notifications: boolean;
  language: string;
  timezone: string;
}

const SettingsContext = createContext<{
  settings: Settings;
  updateSettings: (updates: Partial<Settings>) => void;
} | undefined>(undefined);

const defaultSettings: Settings = {
  notifications: true,
  language: 'en',
  timezone: 'UTC'
};

export function SettingsProvider({ children }: { children: ReactNode }) {
  const [settings, setSettings] = useState<Settings>(() => {
    // 从localStorage初始化
    const stored = localStorage.getItem('settings');
    return stored ? JSON.parse(stored) : defaultSettings;
  });

  // 变化时持久化到localStorage
  useEffect(() => {
    localStorage.setItem('settings', JSON.stringify(settings));
  }, [settings]);

  const updateSettings = (updates: Partial<Settings>) => {
    setSettings(prev => ({ ...prev, ...updates }));
  };

  const value = { settings, updateSettings };

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

export function useSettings() {
  const context = useContext(SettingsContext);
  if (!context) {
    throw new Error('useSettings必须在SettingsProvider内使用');
  }
  return context;
}

用于特性标志的Context

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

interface FeatureFlags {
  newDashboard: boolean;
  betaFeatures: boolean;
  experimentalUI: boolean;
}

const FeatureFlagsContext = createContext<FeatureFlags | undefined>(undefined);

export function FeatureFlagsProvider({
  children,
  flags
}: {
  children: ReactNode;
  flags: FeatureFlags;
}) {
  return (
    <FeatureFlagsContext.Provider value={flags}>
      {children}
    </FeatureFlagsContext.Provider>
  );
}

export function useFeatureFlags() {
  const context = useContext(FeatureFlagsContext);
  if (!context) {
    throw new Error('useFeatureFlags必须在FeatureFlagsProvider内使用');
  }
  return context;
}

export function useFeatureFlag(flag: keyof FeatureFlags): boolean {
  const flags = useFeatureFlags();
  return flags[flag];
}

// 使用
function App() {
  const flags = fetchFeatureFlags(); // 从API或配置获取
  return (
    <FeatureFlagsProvider flags={flags}>
      <Router />
    </FeatureFlagsProvider>
  );
}

function Dashboard() {
  const newDashboard = useFeatureFlag('newDashboard');
  return newDashboard ? <NewDashboard /> : <OldDashboard />;
}

用于通知/弹窗系统的Context

import { createContext, useContext, useState, ReactNode, useCallback } from 'react';

interface Notification {
  id: string;
  type: 'success' | 'error' | 'info' | 'warning';
  message: string;
  duration?: number;
}

interface NotificationContextType {
  notifications: Notification[];
  addNotification: (notification: Omit<Notification, 'id'>) => void;
  removeNotification: (id: string) => void;
}

const NotificationContext = createContext<NotificationContextType | undefined>(
  undefined
);

export function NotificationProvider({ children }: { children: ReactNode }) {
  const [notifications, setNotifications] = useState<Notification[]>([]);

  const addNotification = useCallback(
    (notification: Omit<Notification, 'id'>) => {
      const id = Math.random().toString(36).substr(2, 9);
      const newNotification = { ...notification, id };

      setNotifications(prev => [...prev, newNotification]);

      // 持续时间后自动移除
      if (notification.duration !== 0) {
        setTimeout(() => {
          removeNotification(id);
        }, notification.duration || 5000);
      }
    },
    []
  );

  const removeNotification = useCallback((id: string) => {
    setNotifications(prev => prev.filter(n => n.id !== id));
  }, []);

  const value = { notifications, addNotification, removeNotification };

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

export function useNotifications() {
  const context = useContext(NotificationContext);
  if (!context) {
    throw new Error('useNotifications必须在NotificationProvider内使用');
  }
  return context;
}

function NotificationContainer() {
  const { notifications, removeNotification } = useNotifications();

  return (
    <div className="notification-container">
      {notifications.map(notification => (
        <div
          key={notification.id}
          className={`notification notification-${notification.type}`}
          onClick={() => removeNotification(notification.id)}
        >
          {notification.message}
        </div>
      ))}
    </div>
  );
}

用于模态框管理的Context

import { createContext, useContext, useState, ReactNode, useCallback } from 'react';

interface ModalContextType {
  isOpen: boolean;
  modalContent: ReactNode | null;
  openModal: (content: ReactNode) => void;
  closeModal: () => void;
}

const ModalContext = createContext<ModalContextType | undefined>(undefined);

export function ModalProvider({ children }: { children: ReactNode }) {
  const [isOpen, setIsOpen] = useState(false);
  const [modalContent, setModalContent] = useState<ReactNode | null>(null);

  const openModal = useCallback((content: ReactNode) => {
    setModalContent(content);
    setIsOpen(true);
  }, []);

  const closeModal = useCallback(() => {
    setIsOpen(false);
    // 延迟清除内容以支持动画
    setTimeout(() => setModalContent(null), 300);
  }, []);

  const value = { isOpen, modalContent, openModal, closeModal };

  return (
    <ModalContext.Provider value={value}>
      {children}
      {isOpen && (
        <div className="modal-overlay" onClick={closeModal}>
          <div className="modal-content" onClick={e => e.stopPropagation()}>
            {modalContent}
            <button onClick={closeModal}>关闭</button>
          </div>
        </div>
      )}
    </ModalContext.Provider>
  );
}

export function useModal() {
  const context = useContext(ModalContext);
  if (!context) {
    throw new Error('useModal必须在ModalProvider内使用');
  }
  return context;
}

// 使用
function UserProfile() {
  const { openModal } = useModal();

  const handleEditProfile = () => {
    openModal(<EditProfileForm />);
  };

  return <button onClick={handleEditProfile}>编辑个人资料</button>;
}

用于表单状态管理的Context

import { createContext, useContext, useState, ReactNode } from 'react';

interface FormData {
  [key: string]: any;
}

interface FormContextType {
  formData: FormData;
  errors: Record<string, string>;
  setFieldValue: (field: string, value: any) => void;
  setFieldError: (field: string, error: string) => void;
  clearErrors: () => void;
  resetForm: () => void;
}

const FormContext = createContext<FormContextType | undefined>(undefined);

export function FormProvider({
  children,
  initialValues = {}
}: {
  children: ReactNode;
  initialValues?: FormData;
}) {
  const [formData, setFormData] = useState<FormData>(initialValues);
  const [errors, setErrors] = useState<Record<string, string>>({});

  const setFieldValue = (field: string, value: any) => {
    setFormData(prev => ({ ...prev, [field]: value }));
    // 字段修改时清除错误
    if (errors[field]) {
      setErrors(prev => {
        const newErrors = { ...prev };
        delete newErrors[field];
        return newErrors;
      });
    }
  };

  const setFieldError = (field: string, error: string) => {
    setErrors(prev => ({ ...prev, [field]: error }));
  };

  const clearErrors = () => setErrors({});

  const resetForm = () => {
    setFormData(initialValues);
    setErrors({});
  };

  const value = {
    formData,
    errors,
    setFieldValue,
    setFieldError,
    clearErrors,
    resetForm
  };

  return <FormContext.Provider value={value}>{children}</FormContext.Provider>;
}

export function useForm() {
  const context = useContext(FormContext);
  if (!context) {
    throw new Error('useForm必须在FormProvider内使用');
  }
  return context;
}

// 使用
function LoginForm() {
  return (
    <FormProvider initialValues={{ email: '', password: '' }}>
      <Form />
    </FormProvider>
  );
}

function Form() {
  const { formData, errors, setFieldValue } = useForm();

  return (
    <form>
      <input
        type="email"
        value={formData.email}
        onChange={e => setFieldValue('email', e.target.value)}
      />
      {errors.email && <span>{errors.email}</span>}

      <input
        type="password"
        value={formData.password}
        onChange={e => setFieldValue('password', e.target.value)}
      />
      {errors.password && <span>{errors.password}</span>}
    </form>
  );
}

测试Context Providers

import { render, screen } from '@testing-library/react';
import { AuthProvider, useAuth } from './AuthContext';

function TestComponent() {
  const { user, isAuthenticated } = useAuth();
  return (
    <div>
      <div data-testid="authenticated">{isAuthenticated.toString()}</div>
      <div data-testid="user">{user?.name || 'None'}</div>
    </div>
  );
}

describe('AuthProvider', () => {
  it('提供认证状态', () => {
    render(
      <AuthProvider>
        <TestComponent />
      </AuthProvider>
    );

    expect(screen.getByTestId('authenticated')).toHaveTextContent('false');
    expect(screen.getByTestId('user')).toHaveTextContent('None');
  });

  it('在provider外使用时抛出错误', () => {
    // 抑制此测试的console.error
    const spy = jest.spyOn(console, 'error').mockImplementation();

    expect(() => {
      render(<TestComponent />);
    }).toThrow('useAuth必须在AuthProvider内使用');

    spy.mockRestore();
  });
});

何时使用此技能

在需要时使用react-context-patterns:

  • 共享状态于多个组件之间,避免prop drilling
  • 实现全局应用状态(如认证、主题等)
  • 为复杂功能构建provider模式
  • 创建具有共享状态的复合组件
  • 管理深层嵌套组件通信
  • 实现特定于特性的状态管理
  • 构建可扩展的React应用
  • 避免过度传递props
  • 创建可重用的context模式
  • 管理横切关注点(如通知、模态框等)

最佳实践

  1. 按关注点拆分contexts - 为认证、主题、购物车等创建单独的contexts。不要组合不相关的状态。

  2. 使用useMemo记忆context值 - 防止provider重新渲染时不必要的重新渲染。

  3. 提供自定义hooks - 始终创建像useAuth()这样的自定义hook,而不是直接暴露useContext()

  4. 在provider外抛出错误 - 确保context在正确的provider边界内使用。

  5. 使用TypeScript - 为context值定义适当的类型,以在编译时捕获错误。

  6. 保持值稳定 - 避免在每次渲染时创建新对象/函数。使用useMemouseCallback

  7. 拆分读写contexts - 对于性能关键的应用,分离状态和dispatch contexts。

  8. 文档化context使用 - 清晰记录每个context提供什么以及何时使用它。

  9. 全面测试 - 为providers、自定义hooks和错误情况编写测试。

  10. 考虑替代方案 - 不要对所有内容使用Context。对于某些情况,本地状态、prop传递或状态管理库可能更好。

常见陷阱

  1. 创建太多contexts - Context地狱和prop drilling一样糟糕。将相关状态分组在一起。

  2. 不使用useMemo记忆值 - 如果值不被记忆,每个provider重新渲染都会导致所有消费者重新渲染。

  3. 使用context处理所有状态 - 本地状态更简单且性能更高,适用于组件特定状态。

  4. 忘记错误边界 - 在自定义hooks中始终检查context是否存在,以提供有用的错误消息。

  5. 不提供默认值 - 始终处理context可能不可用的情况。

  6. 过度使用性能 - Context导致所有消费者重新渲染。对于频繁变化的值,考虑替代方案。

  7. 不拆分操作 - 分离读写可以显著提高性能。

  8. 创建不稳定的值 - 在provider内联定义对象或函数会导致不必要的重新渲染。

  9. 用于高频更新 - Context未针对每秒变化多次的值进行优化。

  10. 不考虑组合 - 有时提升状态或使用组合模式比context更简单。

资源