React状态管理

第四章 状态管理

React 应用的状态管理是构建复杂应用的关键。从简单的 useState 到全局状态管理解决方案,本章将深入探讨各种状态管理模式和最佳实践。

4.1 React 内置状态管理

4.1.1 useState Hook 深度解析

useState 基础与高级用法:

import React, { useState, useCallback, useEffect } from 'react';

// 基础 useState 使用
const BasicCounter = () => {
  const [count, setCount] = useState(0);
  const [message, setMessage] = useState('Hello React');

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
      
      <input
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        placeholder="Type a message"
      />
      <p>{message}</p>
    </div>
  );
};

// 函数式更新 - 推荐方式
const FunctionalCounter = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    // 使用函数式更新,确保基于最新状态
    setCount(prevCount => prevCount + 1);
  };

  const incrementByFive = () => {
    // 多次函数式更新
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <h2>Count: {count}</h2>
      <button onClick={increment}>Increment</button>
      <button onClick={incrementByFive}>Add 5</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
};

// 复杂状态管理
const UserProfile = () => {
  const [profile, setProfile] = useState({
    name: '',
    email: '',
    age: 18,
    preferences: {
      theme: 'light',
      notifications: true,
      language: 'en'
    }
  });

  const [errors, setErrors] = useState({});
  const [isLoading, setIsLoading] = useState(false);

  // 部分状态更新
  const updateField = (field, value) => {
    setProfile(prev => ({
      ...prev,
      [field]: value
    }));
  };

  // 深层嵌套状态更新
  const updatePreference = (pref, value) => {
    setProfile(prev => ({
      ...prev,
      preferences: {
        ...prev.preferences,
        [pref]: value
      }
    }));
  };

  // 批量更新状态
  const updateProfile = (newData) => {
    setProfile(prev => ({
      ...prev,
      ...newData
    }));
  };

  // 表单验证
  const validateField = (field, value) => {
    const newErrors = { ...errors };
    
    switch (field) {
      case 'name':
        if (!value.trim()) {
          newErrors.name = 'Name is required';
        } else if (value.length < 2) {
          newErrors.name = 'Name must be at least 2 characters';
        } else {
          delete newErrors.name;
        }
        break;
        
      case 'email':
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(value)) {
          newErrors.email = 'Invalid email format';
        } else {
          delete newErrors.email;
        }
        break;
        
      case 'age':
        if (value < 18 || value > 120) {
          newErrors.age = 'Age must be between 18 and 120';
        } else {
          delete newErrors.age;
        }
        break;
    }
    
    setErrors(newErrors);
  };

  // 处理表单提交
  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);

    try {
      // 验证所有字段
      Object.keys(profile).forEach(field => {
        if (field !== 'preferences') {
          validateField(field, profile[field]);
        }
      });

      if (Object.keys(errors).length === 0) {
        // 提交数据
        const response = await fetch('/api/profile', {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(profile)
        });

        if (response.ok) {
          console.log('Profile updated successfully');
        }
      }
    } catch (error) {
      console.error('Error updating profile:', error);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="profile-form">
      <div className="form-group">
        <label>Name:</label>
        <input
          type="text"
          value={profile.name}
          onChange={(e) => {
            updateField('name', e.target.value);
            validateField('name', e.target.value);
          }}
          className={errors.name ? 'error' : ''}
        />
        {errors.name && <span className="error-message">{errors.name}</span>}
      </div>

      <div className="form-group">
        <label>Email:</label>
        <input
          type="email"
          value={profile.email}
          onChange={(e) => {
            updateField('email', e.target.value);
            validateField('email', e.target.value);
          }}
          className={errors.email ? 'error' : ''}
        />
        {errors.email && <span className="error-message">{errors.email}</span>}
      </div>

      <div className="form-group">
        <label>Age:</label>
        <input
          type="number"
          value={profile.age}
          onChange={(e) => {
            updateField('age', parseInt(e.target.value));
            validateField('age', parseInt(e.target.value));
          }}
          className={errors.age ? 'error' : ''}
        />
        {errors.age && <span className="error-message">{errors.age}</span>}
      </div>

      <div className="form-group">
        <label>Theme:</label>
        <select
          value={profile.preferences.theme}
          onChange={(e) => updatePreference('theme', e.target.value)}
        >
          <option value="light">Light</option>
          <option value="dark">Dark</option>
          <option value="auto">Auto</option>
        </select>
      </div>

      <div className="form-group">
        <label>
          <input
            type="checkbox"
            checked={profile.preferences.notifications}
            onChange={(e) => updatePreference('notifications', e.target.checked)}
          />
          Enable Notifications
        </label>
      </div>

      <div className="form-group">
        <label>Language:</label>
        <select
          value={profile.preferences.language}
          onChange={(e) => updatePreference('language', e.target.value)}
        >
          <option value="en">English</option>
          <option value="zh">中文</option>
          <option value="es">Español</option>
        </select>
      </div>

      <button type="submit" disabled={isLoading || Object.keys(errors).length > 0}>
        {isLoading ? 'Saving...' : 'Save Profile'}
      </button>

      <pre style={{ marginTop: '20px', background: '#f5f5f5', padding: '10px' }}>
        {JSON.stringify(profile, null, 2)}
      </pre>
    </form>
  );
};

useState 性能优化:

// 状态结构优化 - 避免不必要的状态更新
const OptimizedComponent = () => {
  // ❌ 分离的状态可能导致多次渲染
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState('');
  const [isValid, setIsValid] = useState(false);
  const [loading, setLoading] = useState(false);

  // ✅ 相关状态组合
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: ''
  });
  
  const [uiState, setUiState] = useState({
    isValid: false,
    loading: false,
    errors: {}
  });

  // 使用 useReducer 处理复杂状态逻辑
  const formReducer = (state, action) => {
    switch (action.type) {
      case 'SET_FIELD':
        return {
          ...state,
          formData: {
            ...state.formData,
            [action.field]: action.value
          }
        };
        
      case 'SET_LOADING':
        return {
          ...state,
          loading: action.loading
        };
        
      case 'SET_ERRORS':
        return {
          ...state,
          errors: action.errors,
          isValid: Object.keys(action.errors).length === 0
        };
        
      case 'RESET_FORM':
        return {
          formData: { name: '', email: '', age: '' },
          loading: false,
          errors: {},
          isValid: false
        };
        
      default:
        return state;
    }
  };

  const [formState, dispatch] = React.useReducer(formReducer, {
    formData: { name: '', email: '', age: '' },
    loading: false,
    errors: {},
    isValid: false
  });

  // 优化的字段更新
  const updateField = useCallback((field, value) => {
    dispatch({
      type: 'SET_FIELD',
      field,
      value
    });
  }, []);

  // 优化的验证逻辑
  const validateForm = useCallback(() => {
    const errors = {};
    
    if (!formState.formData.name.trim()) {
      errors.name = 'Name is required';
    }
    
    if (!formState.formData.email.trim()) {
      errors.email = 'Email is required';
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formState.formData.email)) {
      errors.email = 'Invalid email format';
    }
    
    if (!formState.formData.age || formState.formData.age < 18) {
      errors.age = 'Must be at least 18 years old';
    }
    
    dispatch({
      type: 'SET_ERRORS',
      errors
    });
    
    return Object.keys(errors).length === 0;
  }, [formState.formData]);

  // 计算属性优化
  const formIsValid = useMemo(() => {
    const { name, email, age } = formState.formData;
    return name.trim() && email.trim() && age >= 18;
  }, [formState.formData]);

  return (
    <div>
      <h2>Optimized Form</h2>
      
      <div className="form-group">
        <label>Name:</label>
        <input
          type="text"
          value={formState.formData.name}
          onChange={(e) => updateField('name', e.target.value)}
        />
        {formState.errors.name && (
          <span className="error">{formState.errors.name}</span>
        )}
      </div>

      <div className="form-group">
        <label>Email:</label>
        <input
          type="email"
          value={formState.formData.email}
          onChange={(e) => updateField('email', e.target.value)}
        />
        {formState.errors.email && (
          <span className="error">{formState.errors.email}</span>
        )}
      </div>

      <div className="form-group">
        <label>Age:</label>
        <input
          type="number"
          value={formState.formData.age}
          onChange={(e) => updateField('age', parseInt(e.target.value))}
        />
        {formState.errors.age && (
          <span className="error">{formState.errors.age}</span>
        )}
      </div>

      <div className="form-actions">
        <button 
          onClick={validateForm}
          disabled={formState.loading}
        >
          {formState.loading ? 'Validating...' : 'Validate'}
        </button>
        
        <button onClick={() => dispatch({ type: 'RESET_FORM' })}>
          Reset
        </button>
      </div>

      <div className="form-status">
        Form is valid: {formState.isValid ? '✅' : '❌'}
      </div>

      <pre>
        Current state: {JSON.stringify(formState, null, 2)}
      </pre>
    </div>
  );
};

4.1.2 useReducer 复杂状态管理

useReducer 基础与高级应用:

import React, { useReducer, useCallback, useEffect } from 'react';

// 购物车状态管理
const shoppingCartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TO_CART': {
      const { product, quantity = 1 } = action.payload;
      const existingItem = state.items.find(item => item.id === product.id);
      
      if (existingItem) {
        return {
          ...state,
          items: state.items.map(item =>
            item.id === product.id
              ? { ...item, quantity: item.quantity + quantity }
              : item
          )
        };
      } else {
        return {
          ...state,
          items: [...state.items, { ...product, quantity }]
        };
      }
    }
    
    case 'REMOVE_FROM_CART': {
      const { productId } = action.payload;
      return {
        ...state,
        items: state.items.filter(item => item.id !== productId)
      };
    }
    
    case 'UPDATE_QUANTITY': {
      const { productId, quantity } = action.payload;
      
      if (quantity <= 0) {
        return {
          ...state,
          items: state.items.filter(item => item.id !== productId)
        };
      }
      
      return {
        ...state,
        items: state.items.map(item =>
          item.id === productId ? { ...item, quantity } : item
        )
      };
    }
    
    case 'APPLY_DISCOUNT': {
      const { discountCode } = action.payload;
      let discountRate = 0;
      
      switch (discountCode) {
        case 'SAVE10':
          discountRate = 0.1;
          break;
        case 'SAVE20':
          discountRate = 0.2;
          break;
        case 'WELCOME':
          discountRate = 0.15;
          break;
        default:
          return {
            ...state,
            discountError: 'Invalid discount code'
          };
      }
      
      return {
        ...state,
        discountCode,
        discountRate,
        discountError: null
      };
    }
    
    case 'CLEAR_CART': {
      return {
        ...state,
        items: [],
        discountCode: null,
        discountRate: 0,
        discountError: null
      };
    }
    
    case 'SET_SHIPPING_METHOD': {
      const { method, cost } = action.payload;
      return {
        ...state,
        shippingMethod: method,
        shippingCost: cost
      };
    }
    
    case 'SET_LOADING': {
      return {
        ...state,
        loading: action.loading
      };
    }
    
    case 'SET_ERROR': {
      return {
        ...state,
        error: action.error
      };
    }
    
    default:
      return state;
  }
};

// 购物车计算属性
const useCartCalculations = (cartState) => {
  const {
    items,
    discountRate,
    shippingCost
  } = cartState;

  const subtotal = React.useMemo(() => {
    return items.reduce((total, item) => total + (item.price * item.quantity), 0);
  }, [items]);

  const discountAmount = React.useMemo(() => {
    return subtotal * discountRate;
  }, [subtotal, discountRate]);

  const total = React.useMemo(() => {
    return Math.max(0, subtotal - discountAmount + shippingCost);
  }, [subtotal, discountAmount, shippingCost]);

  const itemCount = React.useMemo(() => {
    return items.reduce((total, item) => total + item.quantity, 0);
  }, [items]);

  return {
    subtotal,
    discountAmount,
    total,
    itemCount
  };
};

const ShoppingCart = () => {
  const [cartState, dispatch] = useReducer(shoppingCartReducer, {
    items: [],
    discountCode: null,
    discountRate: 0,
    discountError: null,
    shippingMethod: 'standard',
    shippingCost: 0,
    loading: false,
    error: null
  });

  const { subtotal, discountAmount, total, itemCount } = useCartCalculations(cartState);

  // 购物车操作
  const addToCart = useCallback((product, quantity = 1) => {
    dispatch({
      type: 'ADD_TO_CART',
      payload: { product, quantity }
    });
  }, []);

  const removeFromCart = useCallback((productId) => {
    dispatch({
      type: 'REMOVE_FROM_CART',
      payload: { productId }
    });
  }, []);

  const updateQuantity = useCallback((productId, quantity) => {
    dispatch({
      type: 'UPDATE_QUANTITY',
      payload: { productId, quantity }
    });
  }, []);

  const applyDiscount = useCallback((discountCode) => {
    dispatch({
      type: 'APPLY_DISCOUNT',
      payload: { discountCode }
    });
  }, []);

  const clearCart = useCallback(() => {
    dispatch({ type: 'CLEAR_CART' });
  }, []);

  const setShippingMethod = useCallback((method, cost) => {
    dispatch({
      type: 'SET_SHIPPING_METHOD',
      payload: { method, cost }
    });
  }, []);

  // 模拟产品数据
  const products = [
    { id: 1, name: 'Laptop', price: 999.99, category: 'Electronics' },
    { id: 2, name: 'Book', price: 29.99, category: 'Books' },
    { id: 3, name: 'Headphones', price: 199.99, category: 'Electronics' },
    { id: 4, name: 'Coffee Mug', price: 15.99, category: 'Home' },
    { id: 5, name: 'Desk Chair', price: 249.99, category: 'Furniture' }
  ];

  return (
    <div className="shopping-cart">
      <h2>Shopping Cart</h2>
      
      <div className="cart-container">
        {/* 产品列表 */}
        <div className="products-section">
          <h3>Products</h3>
          <div className="product-grid">
            {products.map(product => (
              <div key={product.id} className="product-card">
                <h4>{product.name}</h4>
                <p className="price">${product.price.toFixed(2)}</p>
                <p className="category">{product.category}</p>
                <button 
                  onClick={() => addToCart(product, 1)}
                  className="add-to-cart-btn"
                >
                  Add to Cart
                </button>
              </div>
            ))}
          </div>
        </div>

        {/* 购物车内容 */}
        <div className="cart-section">
          <h3>Cart ({itemCount} items)</h3>
          
          {cartState.items.length === 0 ? (
            <p>Your cart is empty</p>
          ) : (
            <>
              <div className="cart-items">
                {cartState.items.map(item => (
                  <div key={item.id} className="cart-item">
                    <div className="item-info">
                      <h4>{item.name}</h4>
                      <p className="price">${item.price.toFixed(2)}</p>
                    </div>
                    
                    <div className="quantity-controls">
                      <button 
                        onClick={() => updateQuantity(item.id, item.quantity - 1)}
                      >
                        -
                      </button>
                      <span>{item.quantity}</span>
                      <button 
                        onClick={() => updateQuantity(item.id, item.quantity + 1)}
                      >
                        +
                      </button>
                    </div>
                    
                    <div className="item-total">
                      ${(item.price * item.quantity).toFixed(2)}
                    </div>
                    
                    <button 
                      onClick={() => removeFromCart(item.id)}
                      className="remove-btn"
                    >
                      ×
                    </button>
                  </div>
                ))}
              </div>

              {/* 折扣应用 */}
              <div className="discount-section">
                <input
                  type="text"
                  placeholder="Enter discount code"
                  onKeyPress={(e) => {
                    if (e.key === 'Enter') {
                      applyDiscount(e.target.value);
                      e.target.value = '';
                    }
                  }}
                />
                <button onClick={(e) => {
                  const input = e.target.previousElementSibling;
                  applyDiscount(input.value);
                  input.value = '';
                }}>
                  Apply
                </button>
                {cartState.discountError && (
                  <span className="error">{cartState.discountError}</span>
                )}
              </div>

              {/* 配送方式 */}
              <div className="shipping-section">
                <h4>Shipping Method</h4>
                <div className="shipping-options">
                  <label>
                    <input
                      type="radio"
                      name="shipping"
                      checked={cartState.shippingMethod === 'standard'}
                      onChange={() => setShippingMethod('standard', 9.99)}
                    />
                    Standard ($9.99)
                  </label>
                  <label>
                    <input
                      type="radio"
                      name="shipping"
                      checked={cartState.shippingMethod === 'express'}
                      onChange={() => setShippingMethod('express', 19.99)}
                    />
                    Express ($19.99)
                  </label>
                  <label>
                    <input
                      type="radio"
                      name="shipping"
                      checked={cartState.shippingMethod === 'free'}
                      onChange={() => setShippingMethod('free', 0)}
                    />
                    Free (5-7 days)
                  </label>
                </div>
              </div>

              {/* 价格汇总 */}
              <div className="price-summary">
                <div className="price-row">
                  <span>Subtotal:</span>
                  <span>${subtotal.toFixed(2)}</span>
                </div>
                
                {discountAmount > 0 && (
                  <div className="price-row discount">
                    <span>Discount ({cartState.discountCode}):</span>
                    <span>-${discountAmount.toFixed(2)}</span>
                  </div>
                )}
                
                <div className="price-row">
                  <span>Shipping:</span>
                  <span>${cartState.shippingCost.toFixed(2)}</span>
                </div>
                
                <div className="price-row total">
                  <span>Total:</span>
                  <span>${total.toFixed(2)}</span>
                </div>
              </div>

              <div className="cart-actions">
                <button className="checkout-btn">
                  Proceed to Checkout
                </button>
                <button onClick={clearCart} className="clear-btn">
                  Clear Cart
                </button>
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
};

// 任务管理器 - 复杂状态管理示例
const taskReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_TASK': {
      const { title, description, priority = 'medium', dueDate } = action.payload;
      const newTask = {
        id: Date.now(),
        title,
        description,
        priority,
        dueDate,
        status: 'todo',
        createdAt: new Date().toISOString(),
        updatedAt: new Date().toISOString()
      };
      
      return {
        ...state,
        tasks: [...state.tasks, newTask]
      };
    }
    
    case 'UPDATE_TASK': {
      const { taskId, updates } = action.payload;
      return {
        ...state,
        tasks: state.tasks.map(task =>
          task.id === taskId
            ? { ...task, ...updates, updatedAt: new Date().toISOString() }
            : task
        )
      };
    }
    
    case 'DELETE_TASK': {
      const { taskId } = action.payload;
      return {
        ...state,
        tasks: state.tasks.filter(task => task.id !== taskId)
      };
    }
    
    case 'SET_FILTER': {
      return {
        ...state,
        filter: action.filter
      };
    }
    
    case 'SET_SEARCH': {
      return {
        ...state,
        searchTerm: action.searchTerm
      };
    }
    
    case 'SET_SORT': {
      return {
        ...state,
        sortBy: action.sortBy
      };
    }
    
    case 'SET_LOADING': {
      return {
        ...state,
        loading: action.loading
      };
    }
    
    default:
      return state;
  }
};

const TaskManager = () => {
  const [state, dispatch] = React.useReducer(taskReducer, {
    tasks: [],
    filter: 'all', // all, todo, in-progress, done
    searchTerm: '',
    sortBy: 'createdAt', // createdAt, dueDate, priority
    loading: false
  });

  const { tasks, filter, searchTerm, sortBy } = state;

  // 过滤和排序任务
  const filteredAndSortedTasks = React.useMemo(() => {
    let filtered = tasks;

    // 应用状态过滤
    if (filter !== 'all') {
      filtered = filtered.filter(task => task.status === filter);
    }

    // 应用搜索过滤
    if (searchTerm) {
      filtered = filtered.filter(task =>
        task.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
        task.description.toLowerCase().includes(searchTerm.toLowerCase())
      );
    }

    // 应用排序
    filtered.sort((a, b) => {
      switch (sortBy) {
        case 'dueDate':
          return new Date(a.dueDate) - new Date(b.dueDate);
        case 'priority':
          const priorityOrder = { high: 3, medium: 2, low: 1 };
          return priorityOrder[b.priority] - priorityOrder[a.priority];
        case 'createdAt':
        default:
          return new Date(b.createdAt) - new Date(a.createdAt);
      }
    });

    return filtered;
  }, [tasks, filter, searchTerm, sortBy]);

  const addTask = useCallback((taskData) => {
    dispatch({
      type: 'ADD_TASK',
      payload: taskData
    });
  }, []);

  const updateTask = useCallback((taskId, updates) => {
    dispatch({
      type: 'UPDATE_TASK',
      payload: { taskId, updates }
    });
  }, []);

  const deleteTask = useCallback((taskId) => {
    dispatch({
      type: 'DELETE_TASK',
      payload: { taskId }
    });
  }, []);

  const setFilter = useCallback((filter) => {
    dispatch({ type: 'SET_FILTER', filter });
  }, []);

  const setSearchTerm = useCallback((searchTerm) => {
    dispatch({ type: 'SET_SEARCH', searchTerm });
  }, []);

  const setSortBy = useCallback((sortBy) => {
    dispatch({ type: 'SET_SORT', sortBy });
  }, []);

  return (
    <div className="task-manager">
      <h2>Task Manager</h2>
      
      <TaskForm onAddTask={addTask} />
      
      <div className="controls">
        <input
          type="text"
          placeholder="Search tasks..."
          value={searchTerm}
          onChange={(e) => setSearchTerm(e.target.value)}
        />
        
        <select
          value={filter}
          onChange={(e) => setFilter(e.target.value)}
        >
          <option value="all">All Tasks</option>
          <option value="todo">To Do</option>
          <option value="in-progress">In Progress</option>
          <option value="done">Done</option>
        </select>
        
        <select
          value={sortBy}
          onChange={(e) => setSortBy(e.target.value)}
        >
          <option value="createdAt">Created Date</option>
          <option value="dueDate">Due Date</option>
          <option value="priority">Priority</option>
        </select>
      </div>
      
      <TaskList
        tasks={filteredAndSortedTasks}
        onUpdateTask={updateTask}
        onDeleteTask={deleteTask}
      />
      
      <div className="stats">
        Total tasks: {filteredAndSortedTasks.length}
      </div>
    </div>
  );
};

const TaskForm = ({ onAddTask }) => {
  const [formData, setFormData] = useState({
    title: '',
    description: '',
    priority: 'medium',
    dueDate: ''
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    
    if (formData.title.trim()) {
      onAddTask(formData);
      setFormData({
        title: '',
        description: '',
        priority: 'medium',
        dueDate: ''
      });
    }
  };

  return (
    <form onSubmit={handleSubmit} className="task-form">
      <input
        type="text"
        placeholder="Task title"
        value={formData.title}
        onChange={(e) => setFormData({ ...formData, title: e.target.value })}
        required
      />
      
      <textarea
        placeholder="Task description"
        value={formData.description}
        onChange={(e) => setFormData({ ...formData, description: e.target.value })}
      />
      
      <select
        value={formData.priority}
        onChange={(e) => setFormData({ ...formData, priority: e.target.value })}
      >
        <option value="low">Low Priority</option>
        <option value="medium">Medium Priority</option>
        <option value="high">High Priority</option>
      </select>
      
      <input
        type="date"
        value={formData.dueDate}
        onChange={(e) => setFormData({ ...formData, dueDate: e.target.value })}
      />
      
      <button type="submit">Add Task</button>
    </form>
  );
};

const TaskList = ({ tasks, onUpdateTask, onDeleteTask }) => {
  return (
    <div className="task-list">
      {tasks.map(task => (
        <TaskItem
          key={task.id}
          task={task}
          onUpdate={onUpdateTask}
          onDelete={onDeleteTask}
        />
      ))}
    </div>
  );
};

const TaskItem = ({ task, onUpdate, onDelete }) => {
  const [isEditing, setIsEditing] = useState(false);
  const [editData, setEditData] = useState({
    title: task.title,
    description: task.description,
    priority: task.priority,
    status: task.status
  });

  const handleSave = () => {
    onUpdate(task.id, editData);
    setIsEditing(false);
  };

  const handleStatusChange = (newStatus) => {
    onUpdate(task.id, { status: newStatus });
  };

  const priorityColors = {
    high: '#ff4444',
    medium: '#ffaa00',
    low: '#44aa44'
  };

  const statusOptions = ['todo', 'in-progress', 'done'];

  return (
    <div className={`task-item priority-${task.priority}`}>
      <div className="task-header">
        {isEditing ? (
          <input
            type="text"
            value={editData.title}
            onChange={(e) => setEditData({ ...editData, title: e.target.value })}
          />
        ) : (
          <h4>{task.title}</h4>
        )}
        
        <span 
          className="priority-badge"
          style={{ backgroundColor: priorityColors[task.priority] }}
        >
          {task.priority}
        </span>
      </div>

      {isEditing ? (
        <textarea
          value={editData.description}
          onChange={(e) => setEditData({ ...editData, description: e.target.value })}
        />
      ) : (
        <p>{task.description}</p>
      )}

      <div className="task-meta">
        <select
          value={task.status}
          onChange={(e) => handleStatusChange(e.target.value)}
        >
          {statusOptions.map(status => (
            <option key={status} value={status}>
              {status.replace('-', ' ')}
            </option>
          ))}
        </select>

        {task.dueDate && (
          <span className="due-date">
            Due: {new Date(task.dueDate).toLocaleDateString()}
          </span>
        )}
      </div>

      <div className="task-actions">
        {isEditing ? (
          <>
            <button onClick={handleSave}>Save</button>
            <button onClick={() => {
              setIsEditing(false);
              setEditData({
                title: task.title,
                description: task.description,
                priority: task.priority,
                status: task.status
              });
            }}>
              Cancel
            </button>
          </>
        ) : (
          <>
            <button onClick={() => setIsEditing(true)}>Edit</button>
            <button onClick={() => onDelete(task.id)}>Delete</button>
          </>
        )}
      </div>
    </div>
  );
};

通过这节的内容,你已经掌握了 React 内置状态管理的核心概念和高级应用技巧。接下来我们将探讨全局状态管理解决方案。

4.2 Context 全局状态管理

4.2.1 Context 基础与最佳实践

Context 设计模式:

import React, { createContext, useContext, useReducer, useMemo, useCallback } from 'react';

// 1. 创建专门的 Context
const AuthContext = createContext(null);
const ThemeContext = createContext(null);
const NotificationContext = createContext(null);
const UserPreferencesContext = createContext(null);

// 2. Auth Context 状态管理
const authReducer = (state, action) => {
  switch (action.type) {
    case 'LOGIN_START':
      return {
        ...state,
        loading: true,
        error: null
      };
      
    case 'LOGIN_SUCCESS':
      return {
        ...state,
        user: action.payload.user,
        token: action.payload.token,
        isAuthenticated: true,
        loading: false,
        error: null
      };
      
    case 'LOGIN_FAILURE':
      return {
        ...state,
        loading: false,
        error: action.payload.error,
        isAuthenticated: false
      };
      
    case 'LOGOUT':
      return {
        ...state,
        user: null,
        token: null,
        isAuthenticated: false,
        loading: false,
        error: null
      };
      
    case 'UPDATE_USER':
      return {
        ...state,
        user: { ...state.user, ...action.payload }
      };
      
    case 'REFRESH_TOKEN':
      return {
        ...state,
        token: action.payload.token
      };
      
    default:
      return state;
  }
};

const AuthProvider = ({ children }) => {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    token: localStorage.getItem('token'),
    isAuthenticated: false,
    loading: false,
    error: null
  });

  // 检查初始认证状态
  React.useEffect(() => {
    const token = localStorage.getItem('token');
    if (token) {
      // 验证 token 有效性
      verifyToken(token);
    }
  }, []);

  const verifyToken = async (token) => {
    try {
      const response = await fetch('/api/auth/verify', {
        headers: { Authorization: `Bearer ${token}` }
      });
      
      if (response.ok) {
        const userData = await response.json();
        dispatch({
          type: 'LOGIN_SUCCESS',
          payload: { user: userData, token }
        });
      } else {
        localStorage.removeItem('token');
      }
    } catch (error) {
      localStorage.removeItem('token');
    }
  };

  const login = async (credentials) => {
    dispatch({ type: 'LOGIN_START' });
    
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      });
      
      const data = await response.json();
      
      if (response.ok) {
        localStorage.setItem('token', data.token);
        dispatch({
          type: 'LOGIN_SUCCESS',
          payload: { user: data.user, token: data.token }
        });
        return { success: true };
      } else {
        dispatch({
          type: 'LOGIN_FAILURE',
          payload: { error: data.message || 'Login failed' }
        });
        return { success: false, error: data.message };
      }
    } catch (error) {
      dispatch({
        type: 'LOGIN_FAILURE',
        payload: { error: 'Network error' }
      });
      return { success: false, error: 'Network error' };
    }
  };

  const logout = useCallback(() => {
    localStorage.removeItem('token');
    dispatch({ type: 'LOGOUT' });
  }, []);

  const updateUser = useCallback((userData) => {
    dispatch({
      type: 'UPDATE_USER',
      payload: userData
    });
  }, []);

  const refreshToken = useCallback(async () => {
    try {
      const response = await fetch('/api/auth/refresh', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${state.token}`
        }
      });
      
      if (response.ok) {
        const { token } = await response.json();
        localStorage.setItem('token', token);
        dispatch({
          type: 'REFRESH_TOKEN',
          payload: { token }
        });
      } else {
        logout();
      }
    } catch (error) {
      logout();
    }
  }, [state.token, logout]);

  const value = useMemo(() => ({
    ...state,
    login,
    logout,
    updateUser,
    refreshToken
  }), [state, login, logout, updateUser, refreshToken]);

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

// 3. Theme Context
const themeReducer = (state, action) => {
  switch (action.type) {
    case 'SET_THEME':
      return {
        ...state,
        theme: action.theme,
        customColors: action.customColors || state.customColors
      };
      
    case 'SET_CUSTOM_COLOR':
      return {
        ...state,
        customColors: {
          ...state.customColors,
          [action.colorName]: action.colorValue
        }
      };
      
    case 'SET_FONT_SIZE':
      return {
        ...state,
        fontSize: action.fontSize
      };
      
    case 'TOGGLE_DARK_MODE':
      return {
        ...state,
        darkMode: !state.darkMode,
        theme: !state.darkMode ? 'dark' : 'light'
      };
      
    default:
      return state;
  }
};

const ThemeProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(themeReducer, {
    theme: localStorage.getItem('theme') || 'light',
    darkMode: localStorage.getItem('theme') === 'dark',
    fontSize: localStorage.getItem('fontSize') || 'medium',
    customColors: JSON.parse(localStorage.getItem('customColors') || '{}')
  });

  // 保存主题设置到 localStorage
  React.useEffect(() => {
    localStorage.setItem('theme', state.theme);
    localStorage.setItem('fontSize', state.fontSize);
    localStorage.setItem('customColors', JSON.stringify(state.customColors));
    document.body.className = state.theme;
    document.body.style.fontSize = state.fontSize;
  }, [state.theme, state.fontSize, state.customColors]);

  const setTheme = useCallback((theme) => {
    dispatch({ type: 'SET_THEME', theme, darkMode: theme === 'dark' });
  }, []);

  const setCustomColor = useCallback((colorName, colorValue) => {
    dispatch({
      type: 'SET_CUSTOM_COLOR',
      colorName,
      colorValue
    });
  }, []);

  const setFontSize = useCallback((fontSize) => {
    dispatch({ type: 'SET_FONT_SIZE', fontSize });
  }, []);

  const toggleDarkMode = useCallback(() => {
    dispatch({ type: 'TOGGLE_DARK_MODE' });
  }, []);

  const value = useMemo(() => ({
    ...state,
    setTheme,
    setCustomColor,
    setFontSize,
    toggleDarkMode
  }), [state, setTheme, setCustomColor, setFontSize, toggleDarkMode]);

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

// 4. Notification Context
const notificationReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_NOTIFICATION':
      const notification = {
        id: Date.now(),
        ...action.notification
      };
      return {
        ...state,
        notifications: [...state.notifications, notification]
      };
      
    case 'REMOVE_NOTIFICATION':
      return {
        ...state,
        notifications: state.notifications.filter(
          notification => notification.id !== action.id
        )
      };
      
    case 'CLEAR_NOTIFICATIONS':
      return {
        ...state,
        notifications: []
      };
      
    default:
      return state;
  }
};

const NotificationProvider = ({ children }) => {
  const [state, dispatch] = React.useReducer(notificationReducer, {
    notifications: []
  });

  const addNotification = useCallback((notification) => {
    dispatch({
      type: 'ADD_NOTIFICATION',
      notification: {
        type: 'info',
        duration: 5000,
        ...notification
      }
    });

    // 自动移除通知
    if (notification.duration !== 0) {
      const duration = notification.duration || 5000;
      setTimeout(() => {
        dispatch({
          type: 'REMOVE_NOTIFICATION',
          id: Date.now()
        });
      }, duration);
    }
  }, []);

  const removeNotification = useCallback((id) => {
    dispatch({ type: 'REMOVE_NOTIFICATION', id });
  }, []);

  const clearNotifications = useCallback(() => {
    dispatch({ type: 'CLEAR_NOTIFICATIONS' });
  }, []);

  const value = useMemo(() => ({
    notifications: state.notifications,
    addNotification,
    removeNotification,
    clearNotifications,
    success: (message, options) => addNotification({ type: 'success', message, ...options }),
    error: (message, options) => addNotification({ type: 'error', message, ...options }),
    warning: (message, options) => addNotification({ type: 'warning', message, ...options }),
    info: (message, options) => addNotification({ type: 'info', message, ...options })
  }), [state.notifications, addNotification, removeNotification, clearNotifications]);

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

const NotificationContainer = ({ notifications }) => {
  const { removeNotification } = useNotifications();

  return (
    <div className="notification-container">
      {notifications.map(notification => (
        <NotificationItem
          key={notification.id}
          notification={notification}
          onClose={() => removeNotification(notification.id)}
        />
      ))}
    </div>
  );
};

const NotificationItem = ({ notification, onClose }) => {
  React.useEffect(() => {
    if (notification.duration > 0) {
      const timer = setTimeout(onClose, notification.duration);
      return () => clearTimeout(timer);
    }
  }, [notification.duration, onClose]);

  return (
    <div className={`notification notification-${notification.type}`}>
      <div className="notification-content">
        {notification.icon && <span className="notification-icon">{notification.icon}</span>}
        <span className="notification-message">{notification.message}</span>
      </div>
      <button onClick={onClose} className="notification-close">
        ×
      </button>
    </div>
  );
};

// 5. 自定义 Hooks
const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
};

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

const useNotifications = () => {
  const context = useContext(NotificationContext);
  if (!context) {
    throw new Error('useNotifications must be used within NotificationProvider');
  }
  return context;
};

// 6. 组合 Provider
const AppProviders = ({ children }) => {
  return (
    <AuthProvider>
      <ThemeProvider>
        <NotificationProvider>
          {children}
        </NotificationProvider>
      </ThemeProvider>
    </AuthProvider>
  );
};

// 7. 使用示例
const App = () => {
  return (
    <AppProviders>
      <AppLayout />
    </AppProviders>
  );
};

const AppLayout = () => {
  const { user, isAuthenticated, logout } = useAuth();
  const { theme, darkMode, toggleDarkMode, fontSize, setFontSize } = useTheme();
  const { success, error } = useNotifications();

  return (
    <div className={`app-layout ${theme}`} style={{ fontSize }}>
      <header className="app-header">
        <h1>My Application</h1>
        
        <div className="header-controls">
          <button onClick={toggleDarkMode}>
            {darkMode ? '☀️' : '🌙'}
          </button>
          
          <select 
            value={fontSize} 
            onChange={(e) => setFontSize(e.target.value)}
          >
            <option value="small">Small</option>
            <option value="medium">Medium</option>
            <option value="large">Large</option>
          </select>
          
          {isAuthenticated ? (
            <div className="user-info">
              <span>Welcome, {user.name}!</span>
              <button onClick={logout}>Logout</button>
            </div>
          ) : (
            <LoginModal />
          )}
        </div>
      </header>
      
      <main className="app-main">
        <Dashboard />
      </main>
    </div>
  );
};

const LoginModal = () => {
  const { login, loading, error } = useAuth();
  const { success } = useNotifications();
  const [credentials, setCredentials] = useState({ username: '', password: '' });

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const result = await login(credentials);
    
    if (result.success) {
      success('Login successful!');
    } else {
      error(result.error || 'Login failed');
    }
  };

  return (
    <div className="login-modal">
      <form onSubmit={handleSubmit}>
        <h2>Login</h2>
        
        {error && <div className="error">{error}</div>}
        
        <input
          type="text"
          placeholder="Username"
          value={credentials.username}
          onChange={(e) => setCredentials({ ...credentials, username: e.target.value })}
          required
        />
        
        <input
          type="password"
          placeholder="Password"
          value={credentials.password}
          onChange={(e) => setCredentials({ ...credentials, password: e.target.value })}
          required
        />
        
        <button type="submit" disabled={loading}>
          {loading ? 'Logging in...' : 'Login'}
        </button>
      </form>
    </div>
  );
};

const Dashboard = () => {
  const { user, isAuthenticated } = useAuth();
  const { theme, setTheme, setCustomColor } = useTheme();
  const { success, error, warning, info } = useNotifications();

  if (!isAuthenticated) {
    return <div>Please log in to access the dashboard.</div>;
  }

  return (
    <div className="dashboard">
      <h2>Welcome to Dashboard, {user.name}!</h2>
      
      <div className="dashboard-section">
        <h3>Theme Settings</h3>
        
        <div className="theme-options">
          <button onClick={() => setTheme('light')}>Light Theme</button>
          <button onClick={() => setTheme('dark')}>Dark Theme</button>
          <button onClick={() => setTheme('blue')}>Blue Theme</button>
        </div>
        
        <div className="color-customization">
          <label>
            Primary Color:
            <input
              type="color"
              onChange={(e) => setCustomColor('primary', e.target.value)}
            />
          </label>
          
          <label>
            Secondary Color:
            <input
              type="color"
              onChange={(e) => setCustomColor('secondary', e.target.value)}
            />
          </label>
        </div>
      </div>
      
      <div className="dashboard-section">
        <h3>Notification Testing</h3>
        
        <div className="notification-tests">
          <button onClick={() => success('Operation completed successfully!')}>
            Success Notification
          </button>
          <button onClick={() => error('An error occurred!')}>
            Error Notification
          </button>
          <button onClick={() => warning('Warning message!')}>
            Warning Notification
          </button>
          <button onClick={() => info('Information message!')}>
            Info Notification
          </button>
        </div>
      </div>
      
      <div className="dashboard-section">
        <h3>User Profile</h3>
        <UserProfile />
      </div>
    </div>
  );
};

const UserProfile = () => {
  const { user, updateUser } = useAuth();
  const { success } = useNotifications();
  const [isEditing, setIsEditing] = useState(false);
  const [editData, setEditData] = useState({
    name: user.name,
    email: user.email
  });

  const handleSave = () => {
    updateUser(editData);
    setIsEditing(false);
    success('Profile updated successfully!');
  };

  return (
    <div className="user-profile">
      {isEditing ? (
        <div>
          <input
            type="text"
            value={editData.name}
            onChange={(e) => setEditData({ ...editData, name: e.target.value })}
          />
          <input
            type="email"
            value={editData.email}
            onChange={(e) => setEditData({ ...editData, email: e.target.value })}
          />
          <button onClick={handleSave}>Save</button>
          <button onClick={() => {
            setIsEditing(false);
            setEditData({ name: user.name, email: user.email });
          }}>Cancel</button>
        </div>
      ) : (
        <div>
          <p><strong>Name:</strong> {user.name}</p>
          <p><strong>Email:</strong> {user.email}</p>
          <button onClick={() => setIsEditing(true)}>Edit Profile</button>
        </div>
      )}
    </div>
  );
};

4.2.2 分层 Context 架构

领域驱动 Context 设计:

// 1. 领域 Context 分离
const ProductContext = createContext(null);
const CartContext = createContext(null);
const OrderContext = createContext(null);
const CustomerContext = createContext(null);

// 2. Product Context - 产品领域
const productReducer = (state, action) => {
  switch (action.type) {
    case 'SET_PRODUCTS':
      return {
        ...state,
        products: action.products,
        loading: false,
        error: null
      };
      
    case 'SET_CATEGORIES':
      return {
        ...state,
        categories: action.categories
      };
      
    case 'SET_FILTERS':
      return {
        ...state,
        filters: { ...state.filters, ...action.filters }
      };
      
    case 'SET_LOADING':
      return {
        ...state,
        loading: action.loading
      };
      
    case 'SET_ERROR':
      return {
        ...state,
        error: action.error,
        loading: false
      };
      
    default:
      return state;
  }
};

const ProductProvider = ({ children }) => {
  const [state, dispatch] = useReducer(productReducer, {
    products: [],
    categories: [],
    filters: {
      category: 'all',
      priceRange: [0, 1000],
      inStock: true,
      sortBy: 'name'
    },
    loading: true,
    error: null
  });

  // API 调用函数
  const fetchProducts = useCallback(async (filters = {}) => {
    dispatch({ type: 'SET_LOADING', loading: true });
    
    try {
      const queryParams = new URLSearchParams();
      
      if (filters.category && filters.category !== 'all') {
        queryParams.append('category', filters.category);
      }
      if (filters.minPrice) {
        queryParams.append('minPrice', filters.minPrice);
      }
      if (filters.maxPrice) {
        queryParams.append('maxPrice', filters.maxPrice);
      }
      if (filters.inStock) {
        queryParams.append('inStock', 'true');
      }
      if (filters.sortBy) {
        queryParams.append('sortBy', filters.sortBy);
      }
      
      const response = await fetch(`/api/products?${queryParams}`);
      const products = await response.json();
      
      dispatch({ type: 'SET_PRODUCTS', products });
    } catch (error) {
      dispatch({ type: 'SET_ERROR', error: error.message });
    }
  }, []);

  const fetchCategories = useCallback(async () => {
    try {
      const response = await fetch('/api/categories');
      const categories = await response.json();
      dispatch({ type: 'SET_CATEGORIES', categories });
    } catch (error) {
      console.error('Failed to fetch categories:', error);
    }
  }, []);

  const setFilters = useCallback((filters) => {
    dispatch({ type: 'SET_FILTERS', filters });
    fetchProducts({ ...state.filters, ...filters });
  }, [state.filters, fetchProducts]);

  const getProductById = useCallback((id) => {
    return state.products.find(product => product.id === parseInt(id));
  }, [state.products]);

  // 初始化数据
  React.useEffect(() => {
    fetchCategories();
    fetchProducts(state.filters);
  }, []);

  const value = useMemo(() => ({
    ...state,
    fetchProducts,
    fetchCategories,
    setFilters,
    getProductById
  }), [state, fetchProducts, fetchCategories, setFilters, getProductById]);

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

// 3. Cart Context - 购物车领域
const cartReducer = (state, action) => {
  switch (action.type) {
    case 'ADD_ITEM': {
      const { product, quantity = 1 } = action.payload;
      const existingItem = state.items.find(item => item.id === product.id);
      
      if (existingItem) {
        return {
          ...state,
          items: state.items.map(item =>
            item.id === product.id
              ? { ...item, quantity: item.quantity + quantity }
              : item
          )
        };
      } else {
        return {
          ...state,
          items: [...state.items, { ...product, quantity }]
        };
      }
    }
    
    case 'REMOVE_ITEM': {
      return {
        ...state,
        items: state.items.filter(item => item.id !== action.productId)
      };
    }
    
    case 'UPDATE_QUANTITY': {
      const { productId, quantity } = action.payload;
      
      if (quantity <= 0) {
        return {
          ...state,
          items: state.items.filter(item => item.id !== productId)
        };
      }
      
      return {
        ...state,
        items: state.items.map(item =>
          item.id === productId ? { ...item, quantity } : item
        )
      };
    }
    
    case 'CLEAR_CART':
      return {
        ...state,
        items: []
      };
      
    case 'APPLY_COUPON': {
      const { code, discount } = action.payload;
      return {
        ...state,
        couponCode: code,
        couponDiscount: discount
      };
    }
    
    case 'REMOVE_COUPON':
      return {
        ...state,
        couponCode: null,
        couponDiscount: 0
      };
      
    default:
      return state;
  }
};

const CartProvider = ({ children }) => {
  const [state, dispatch] = useReducer(cartReducer, {
    items: [],
    couponCode: null,
    couponDiscount: 0
  });

  // 计算属性
  const cartCalculations = useMemo(() => {
    const subtotal = state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
    const itemCount = state.items.reduce((total, item) => total + item.quantity, 0);
    const discount = subtotal * (state.couponDiscount / 100);
    const total = Math.max(0, subtotal - discount);
    
    return {
      subtotal,
      itemCount,
      discount,
      total
    };
  }, [state.items, state.couponDiscount]);

  const addItem = useCallback((product, quantity) => {
    dispatch({ type: 'ADD_ITEM', payload: { product, quantity } });
  }, []);

  const removeItem = useCallback((productId) => {
    dispatch({ type: 'REMOVE_ITEM', productId });
  }, []);

  const updateQuantity = useCallback((productId, quantity) => {
    dispatch({ type: 'UPDATE_QUANTITY', payload: { productId, quantity } });
  }, []);

  const clearCart = useCallback(() => {
    dispatch({ type: 'CLEAR_CART' });
  }, []);

  const applyCoupon = useCallback(async (code) => {
    try {
      const response = await fetch('/api/coupons/validate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code })
      });
      
      if (response.ok) {
        const { discount } = await response.json();
        dispatch({ type: 'APPLY_COUPON', payload: { code, discount } });
        return { success: true, discount };
      } else {
        const { error } = await response.json();
        return { success: false, error };
      }
    } catch (error) {
      return { success: false, error: 'Failed to validate coupon' };
    }
  }, []);

  const removeCoupon = useCallback(() => {
    dispatch({ type: 'REMOVE_COUPON' });
  }, []);

  const value = useMemo(() => ({
    ...state,
    ...cartCalculations,
    addItem,
    removeItem,
    updateQuantity,
    clearCart,
    applyCoupon,
    removeCoupon
  }), [state, cartCalculations, addItem, removeItem, updateQuantity, clearCart, applyCoupon, removeCoupon]);

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

// 4. Order Context - 订单领域
const orderReducer = (state, action) => {
  switch (action.type) {
    case 'CREATE_ORDER':
      return {
        ...state,
        orders: [action.order, ...state.orders],
        loading: false
      };
      
    case 'SET_ORDERS':
      return {
        ...state,
        orders: action.orders,
        loading: false
      };
      
    case 'UPDATE_ORDER_STATUS':
      return {
        ...state,
        orders: state.orders.map(order =>
          order.id === action.orderId
            ? { ...order, status: action.status, updatedAt: new Date().toISOString() }
            : order
        )
      };
      
    case 'SET_LOADING':
      return {
        ...state,
        loading: action.loading
      };
      
    case 'SET_ERROR':
      return {
        ...state,
        error: action.error,
        loading: false
      };
      
    default:
      return state;
  }
};

const OrderProvider = ({ children }) => {
  const [state, dispatch] = useReducer(orderReducer, {
    orders: [],
    loading: false,
    error: null
  });

  const createOrder = useCallback(async (orderData) => {
    dispatch({ type: 'SET_LOADING', loading: true });
    
    try {
      const response = await fetch('/api/orders', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(orderData)
      });
      
      if (response.ok) {
        const order = await response.json();
        dispatch({ type: 'CREATE_ORDER', order });
        return { success: true, order };
      } else {
        const { error } = await response.json();
        dispatch({ type: 'SET_ERROR', error });
        return { success: false, error };
      }
    } catch (error) {
      dispatch({ type: 'SET_ERROR', error: error.message });
      return { success: false, error: error.message };
    }
  }, []);

  const fetchOrders = useCallback(async (filters = {}) => {
    dispatch({ type: 'SET_LOADING', loading: true });
    
    try {
      const queryParams = new URLSearchParams(filters);
      const response = await fetch(`/api/orders?${queryParams}`);
      const orders = await response.json();
      
      dispatch({ type: 'SET_ORDERS', orders });
    } catch (error) {
      dispatch({ type: 'SET_ERROR', error: error.message });
    }
  }, []);

  const updateOrderStatus = useCallback(async (orderId, status) => {
    try {
      const response = await fetch(`/api/orders/${orderId}/status`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ status })
      });
      
      if (response.ok) {
        dispatch({ type: 'UPDATE_ORDER_STATUS', orderId, status });
        return { success: true };
      } else {
        const { error } = await response.json();
        return { success: false, error };
      }
    } catch (error) {
      return { success: false, error: error.message };
    }
  }, []);

  const value = useMemo(() => ({
    ...state,
    createOrder,
    fetchOrders,
    updateOrderStatus
  }), [state, createOrder, fetchOrders, updateOrderStatus]);

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

// 5. 自定义 Hooks
const useProducts = () => {
  const context = useContext(ProductContext);
  if (!context) throw new Error('useProducts must be used within ProductProvider');
  return context;
};

const useCart = () => {
  const context = useContext(CartContext);
  if (!context) throw new Error('useCart must be used within CartProvider');
  return context;
};

const useOrders = () => {
  const context = useContext(OrderContext);
  if (!context) throw new Error('useOrders must be used within OrderProvider');
  return context;
};

// 6. E-commerce 应用组合
const ECommerceApp = ({ children }) => {
  return (
    <ProductProvider>
      <CartProvider>
        <OrderProvider>
          <NotificationProvider>
            {children}
          </NotificationProvider>
        </OrderProvider>
      </CartProvider>
    </ProductProvider>
  );
};

// 7. 使用示例
const ShoppingApp = () => {
  const { products, categories, filters, setFilters } = useProducts();
  const { items, total, addItem, removeItem, updateQuantity, applyCoupon } = useCart();
  const { createOrder, orders } = useOrders();
  const { success, error } = useNotifications();

  const handleCheckout = async () => {
    const orderData = {
      items: items.map(item => ({
        productId: item.id,
        quantity: item.quantity,
        price: item.price
      })),
      total,
      status: 'pending'
    };

    const result = await createOrder(orderData);
    
    if (result.success) {
      success('Order created successfully!');
      // 清空购物车等操作
    } else {
      error(result.error || 'Failed to create order');
    }
  };

  return (
    <div className="shopping-app">
      <ProductFilters 
        categories={categories} 
        filters={filters} 
        setFilters={setFilters} 
      />
      
      <ProductGrid products={products} onAddToCart={addItem} />
      
      <ShoppingCart 
        items={items} 
        total={total}
        onRemoveItem={removeItem}
        onUpdateQuantity={updateQuantity}
        onApplyCoupon={applyCoupon}
        onCheckout={handleCheckout}
      />
      
      {orders.length > 0 && <OrderHistory orders={orders} />}
    </div>
  );
};

通过这些 Context 设计模式,你可以构建出清晰、可维护的分层状态管理架构,每个领域都有独立的管理逻辑和清晰的数据流。

4.3 Redux 状态管理

4.3.1 Redux 基础概念与架构

Redux 核心概念:

// 1. 安装依赖
// npm install @reduxjs/toolkit react-redux

// 2. Redux Toolkit 配置
import { configureStore, createSlice, createAsyncThunk } from '@reduxjs/toolkit';

// 3. 异步 Thunk - 用户数据获取
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async (_, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/users');
      if (!response.ok) {
        throw new Error('Failed to fetch users');
      }
      const data = await response.json();
      return data;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

export const createUser = createAsyncThunk(
  'users/createUser',
  async (userData, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(userData)
      });
      if (!response.ok) {
        throw new Error('Failed to create user');
      }
      const data = await response.json();
      return data;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

// 4. 用户 Slice
const usersSlice = createSlice({
  name: 'users',
  initialState: {
    users: [],
    currentUser: null,
    loading: false,
    error: null,
    filters: {
      search: '',
      role: 'all',
      status: 'all'
    }
  },
  reducers: {
    // 同步 actions
    setCurrentUser: (state, action) => {
      state.currentUser = action.payload;
    },
    clearCurrentUser: (state) => {
      state.currentUser = null;
    },
    setFilters: (state, action) => {
      state.filters = { ...state.filters, ...action.payload };
    },
    clearError: (state) => {
      state.error = null;
    },
    // 本地更新用户
    updateUserLocal: (state, action) => {
      const { userId, updates } = action.payload;
      const userIndex = state.users.findIndex(user => user.id === userId);
      if (userIndex !== -1) {
        state.users[userIndex] = { ...state.users[userIndex], ...updates };
      }
    },
    // 本地删除用户
    deleteUserLocal: (state, action) => {
      const userId = action.payload;
      state.users = state.users.filter(user => user.id !== userId);
    }
  },
  // 异步 actions 的 reducers
  extraReducers: (builder) => {
    builder
      // fetchUsers
      .addCase(fetchUsers.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.loading = false;
        state.users = action.payload;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      })
      // createUser
      .addCase(createUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(createUser.fulfilled, (state, action) => {
        state.loading = false;
        state.users.push(action.payload);
        state.currentUser = action.payload;
      })
      .addCase(createUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.payload;
      });
  }
});

// 导出 actions
export const {
  setCurrentUser,
  clearCurrentUser,
  setFilters,
  clearError,
  updateUserLocal,
  deleteUserLocal
} = usersSlice.actions;

// 导出 reducer
export default usersSlice.reducer;

// 5. 认证 Slice
const authSlice = createSlice({
  name: 'auth',
  initialState: {
    token: localStorage.getItem('token'),
    isAuthenticated: false,
    user: null,
    loading: false,
    error: null
  },
  reducers: {
    loginStart: (state) => {
      state.loading = true;
      state.error = null;
    },
    loginSuccess: (state, action) => {
      state.loading = false;
      state.isAuthenticated = true;
      state.token = action.payload.token;
      state.user = action.payload.user;
      localStorage.setItem('token', action.payload.token);
    },
    loginFailure: (state, action) => {
      state.loading = false;
      state.error = action.payload;
      state.isAuthenticated = false;
      state.token = null;
      state.user = null;
      localStorage.removeItem('token');
    },
    logout: (state) => {
      state.isAuthenticated = false;
      state.token = null;
      state.user = null;
      localStorage.removeItem('token');
    },
    updateUser: (state, action) => {
      state.user = { ...state.user, ...action.payload };
    },
    clearError: (state) => {
      state.error = null;
    }
  }
});

export const {
  loginStart,
  loginSuccess,
  loginFailure,
  logout,
  updateUser,
  clearError: clearAuthError
} = authSlice.actions;

export default authSlice.reducer;

// 6. 产品 Slice
const productsSlice = createSlice({
  name: 'products',
  initialState: {
    products: [],
    categories: [],
    currentProduct: null,
    loading: false,
    error: null,
    filters: {
      category: 'all',
      priceRange: [0, 1000],
      inStock: true,
      search: '',
      sortBy: 'name'
    },
    pagination: {
      page: 1,
      limit: 20,
      total: 0
    }
  },
  reducers: {
    setProducts: (state, action) => {
      state.products = action.payload;
    },
    setCategories: (state, action) => {
      state.categories = action.payload;
    },
    setCurrentProduct: (state, action) => {
      state.currentProduct = action.payload;
    },
    setFilters: (state, action) => {
      state.filters = { ...state.filters, ...action.payload };
    },
    setPagination: (state, action) => {
      state.pagination = { ...state.pagination, ...action.payload };
    },
    setLoading: (state, action) => {
      state.loading = action.payload;
    },
    setError: (state, action) => {
      state.error = action.payload;
    },
    clearError: (state) => {
      state.error = null;
    }
  }
});

export const {
  setProducts,
  setCategories,
  setCurrentProduct,
  setFilters: setProductFilters,
  setPagination,
  setLoading: setProductLoading,
  setError: setProductError,
  clearError: clearProductError
} = productsSlice.actions;

export default productsSlice.reducer;

// 7. 购物车 Slice
const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [],
    total: 0,
    itemCount: 0,
    loading: false,
    error: null,
    couponCode: null,
    couponDiscount: 0
  },
  reducers: {
    addItem: (state, action) => {
      const { product, quantity = 1 } = action.payload;
      const existingItem = state.items.find(item => item.id === product.id);
      
      if (existingItem) {
        existingItem.quantity += quantity;
      } else {
        state.items.push({ ...product, quantity });
      }
      
      // 重新计算总计
      state.itemCount = state.items.reduce((total, item) => total + item.quantity, 0);
      state.total = state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
    },
    removeItem: (state, action) => {
      const productId = action.payload;
      state.items = state.items.filter(item => item.id !== productId);
      
      // 重新计算总计
      state.itemCount = state.items.reduce((total, item) => total + item.quantity, 0);
      state.total = state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
    },
    updateQuantity: (state, action) => {
      const { productId, quantity } = action.payload;
      
      if (quantity <= 0) {
        state.items = state.items.filter(item => item.id !== productId);
      } else {
        const item = state.items.find(item => item.id === productId);
        if (item) {
          item.quantity = quantity;
        }
      }
      
      // 重新计算总计
      state.itemCount = state.items.reduce((total, item) => total + item.quantity, 0);
      state.total = state.items.reduce((total, item) => total + (item.price * item.quantity), 0);
    },
    clearCart: (state) => {
      state.items = [];
      state.total = 0;
      state.itemCount = 0;
      state.couponCode = null;
      state.couponDiscount = 0;
    },
    applyCoupon: (state, action) => {
      const { code, discount } = action.payload;
      state.couponCode = code;
      state.couponDiscount = discount;
    },
    removeCoupon: (state) => {
      state.couponCode = null;
      state.couponDiscount = 0;
    }
  }
});

export const {
  addItem,
  removeItem,
  updateQuantity,
  clearCart,
  applyCoupon,
  removeCoupon
} = cartSlice.actions;

export default cartSlice.reducer;

// 8. 配置 Store
const store = configureStore({
  reducer: {
    users: usersSlice,
    auth: authSlice,
    products: productsSlice,
    cart: cartSlice
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        // 忽略这些 action types 的序列化检查
        ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
        // 忽略这些字段的序列化检查
        ignoredPaths: ['users.currentUser.lastLogin']
      }
    }),
  devTools: process.env.NODE_ENV !== 'production'
});

export default store;

// 9. 根组件设置
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { persistStore, persistReducer } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
import storage from 'redux-persist/lib/storage';

// 持久化配置
const persistConfig = {
  key: 'root',
  storage,
  whitelist: ['auth', 'cart'] // 只持久化 auth 和 cart
};

const persistedReducer = persistReducer(persistConfig, store);

// 在应用中使用
const App = () => {
  return (
    <Provider store={store}>
      <PersistGate loading={<div>Loading...</div>} persistor={persistStore(store)}>
        <MainApp />
      </PersistGate>
    </Provider>
  );
};

4.3.2 React-Redux 集成使用

Hooks 与组件集成:

import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
  fetchUsers,
  createUser,
  setCurrentUser,
  setFilters,
  updateUserLocal,
  deleteUserLocal
} from './slices/usersSlice';
import {
  loginStart,
  loginSuccess,
  loginFailure,
  logout,
  updateUser
} from './slices/authSlice';
import {
  setProducts,
  setProductFilters,
  setCurrentProduct
} from './slices/productsSlice';
import {
  addItem,
  removeItem,
  updateQuantity,
  clearCart,
  applyCoupon
} from './slices/cartSlice';

// 1. 用户管理组件
const UserManagement = () => {
  const dispatch = useDispatch();
  const {
    users,
    loading,
    error,
    filters,
    currentUser
  } = useSelector(state => state.users);

  // 组件挂载时获取用户
  useEffect(() => {
    dispatch(fetchUsers());
  }, [dispatch]);

  // 过滤用户
  const filteredUsers = React.useMemo(() => {
    return users.filter(user => {
      if (filters.search && !user.name.toLowerCase().includes(filters.search.toLowerCase())) {
        return false;
      }
      if (filters.role !== 'all' && user.role !== filters.role) {
        return false;
      }
      if (filters.status !== 'all' && user.status !== filters.status) {
        return false;
      }
      return true;
    });
  }, [users, filters]);

  const handleFilterChange = (filterType, value) => {
    dispatch(setFilters({ [filterType]: value }));
  };

  const handleSelectUser = (user) => {
    dispatch(setCurrentUser(user));
  };

  const handleCreateUser = (userData) => {
    dispatch(createUser(userData));
  };

  const handleUpdateUser = (userId, updates) => {
    dispatch(updateUserLocal({ userId, updates }));
  };

  const handleDeleteUser = (userId) => {
    dispatch(deleteUserLocal(userId));
  };

  if (loading) {
    return <div>Loading users...</div>;
  }

  if (error) {
    return <div>Error: {error}</div>;
  }

  return (
    <div className="user-management">
      <h2>User Management</h2>
      
      <UserFilters 
        filters={filters} 
        onFilterChange={handleFilterChange} 
      />
      
      <CreateUserForm onSubmit={handleCreateUser} />
      
      <UserList 
        users={filteredUsers}
        onSelect={handleSelectUser}
        onUpdate={handleUpdateUser}
        onDelete={handleDeleteUser}
        selectedUserId={currentUser?.id}
      />
      
      {currentUser && (
        <UserDetails 
          user={currentUser} 
          onUpdate={handleUpdateUser}
        />
      )}
    </div>
  );
};

const UserFilters = ({ filters, onFilterChange }) => {
  return (
    <div className="user-filters">
      <input
        type="text"
        placeholder="Search users..."
        value={filters.search}
        onChange={(e) => onFilterChange('search', e.target.value)}
      />
      
      <select
        value={filters.role}
        onChange={(e) => onFilterChange('role', e.target.value)}
      >
        <option value="all">All Roles</option>
        <option value="admin">Admin</option>
        <option value="user">User</option>
        <option value="moderator">Moderator</option>
      </select>
      
      <select
        value={filters.status}
        onChange={(e) => onFilterChange('status', e.target.value)}
      >
        <option value="all">All Status</option>
        <option value="active">Active</option>
        <option value="inactive">Inactive</option>
        <option value="suspended">Suspended</option>
      </select>
    </div>
  );
};

const CreateUserForm = ({ onSubmit }) => {
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    role: 'user',
    status: 'active'
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    onSubmit(formData);
    setFormData({ name: '', email: '', role: 'user', status: 'active' });
  };

  return (
    <form onSubmit={handleSubmit} className="create-user-form">
      <h3>Create New User</h3>
      
      <input
        type="text"
        placeholder="Name"
        value={formData.name}
        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
        required
      />
      
      <input
        type="email"
        placeholder="Email"
        value={formData.email}
        onChange={(e) => setFormData({ ...formData, email: e.target.value })}
        required
      />
      
      <select
        value={formData.role}
        onChange={(e) => setFormData({ ...formData, role: e.target.value })}
      >
        <option value="user">User</option>
        <option value="moderator">Moderator</option>
        <option value="admin">Admin</option>
      </select>
      
      <select
        value={formData.status}
        onChange={(e) => setFormData({ ...formData, status: e.target.value })}
      >
        <option value="active">Active</option>
        <option value="inactive">Inactive</option>
      </select>
      
      <button type="submit">Create User</button>
    </form>
  );
};

const UserList = ({ users, onSelect, onUpdate, onDelete, selectedUserId }) => {
  const [editingUserId, setEditingUserId] = useState(null);
  const [editData, setEditData] = useState({});

  const handleEdit = (user) => {
    setEditingUserId(user.id);
    setEditData({ role: user.role, status: user.status });
  };

  const handleSave = (userId) => {
    onUpdate(userId, editData);
    setEditingUserId(null);
    setEditData({});
  };

  const handleCancel = () => {
    setEditingUserId(null);
    setEditData({});
  };

  return (
    <div className="user-list">
      <h3>Users ({users.length})</h3>
      
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Role</th>
            <th>Status</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {users.map(user => (
            <tr 
              key={user.id}
              className={user.id === selectedUserId ? 'selected' : ''}
              onClick={() => onSelect(user)}
            >
              <td>{user.name}</td>
              <td>{user.email}</td>
              <td>
                {editingUserId === user.id ? (
                  <select
                    value={editData.role}
                    onChange={(e) => setEditData({ ...editData, role: e.target.value })}
                    onClick={(e) => e.stopPropagation()}
                  >
                    <option value="user">User</option>
                    <option value="moderator">Moderator</option>
                    <option value="admin">Admin</option>
                  </select>
                ) : (
                  <span className={`role ${user.role}`}>{user.role}</span>
                )}
              </td>
              <td>
                {editingUserId === user.id ? (
                  <select
                    value={editData.status}
                    onChange={(e) => setEditData({ ...editData, status: e.target.value })}
                    onClick={(e) => e.stopPropagation()}
                  >
                    <option value="active">Active</option>
                    <option value="inactive">Inactive</option>
                    <option value="suspended">Suspended</option>
                  </select>
                ) : (
                  <span className={`status ${user.status}`}>{user.status}</span>
                )}
              </td>
              <td>
                {editingUserId === user.id ? (
                  <div onClick={(e) => e.stopPropagation()}>
                    <button onClick={() => handleSave(user.id)}>Save</button>
                    <button onClick={handleCancel}>Cancel</button>
                  </div>
                ) : (
                  <div>
                    <button onClick={(e) => {
                      e.stopPropagation();
                      handleEdit(user);
                    }}>Edit</button>
                    <button onClick={(e) => {
                      e.stopPropagation();
                      onDelete(user.id);
                    }}>Delete</button>
                  </div>
                )}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

const UserDetails = ({ user, onUpdate }) => {
  const [isEditing, setIsEditing] = useState(false);
  const [editData, setEditData] = useState({});

  const handleEdit = () => {
    setIsEditing(true);
    setEditData({
      name: user.name,
      email: user.email,
      bio: user.bio || ''
    });
  };

  const handleSave = () => {
    onUpdate(user.id, editData);
    setIsEditing(false);
  };

  const handleCancel = () => {
    setIsEditing(false);
    setEditData({});
  };

  return (
    <div className="user-details">
      <h3>User Details</h3>
      
      {isEditing ? (
        <div>
          <input
            type="text"
            value={editData.name}
            onChange={(e) => setEditData({ ...editData, name: e.target.value })}
          />
          <input
            type="email"
            value={editData.email}
            onChange={(e) => setEditData({ ...editData, email: e.target.value })}
          />
          <textarea
            value={editData.bio}
            onChange={(e) => setEditData({ ...editData, bio: e.target.value })}
            placeholder="Bio"
          />
          <button onClick={handleSave}>Save</button>
          <button onClick={handleCancel}>Cancel</button>
        </div>
      ) : (
        <div>
          <p><strong>Name:</strong> {user.name}</p>
          <p><strong>Email:</strong> {user.email}</p>
          <p><strong>Role:</strong> {user.role}</p>
          <p><strong>Status:</strong> {user.status}</p>
          <p><strong>Bio:</strong> {user.bio || 'No bio provided'}</p>
          <button onClick={handleEdit}>Edit</button>
        </div>
      )}
    </div>
  );
};

// 2. 产品和购物车组件
const ShoppingApp = () => {
  const dispatch = useDispatch();
  const { products, filters, loading } = useSelector(state => state.products);
  const { items, total, itemCount } = useSelector(state => state.cart);

  useEffect(() => {
    // 模拟获取产品数据
    const mockProducts = [
      { id: 1, name: 'Laptop', price: 999.99, category: 'Electronics', inStock: true },
      { id: 2, name: 'Book', price: 29.99, category: 'Books', inStock: true },
      { id: 3, name: 'Headphones', price: 199.99, category: 'Electronics', inStock: false },
      { id: 4, name: 'Coffee Mug', price: 15.99, category: 'Home', inStock: true }
    ];
    dispatch(setProducts(mockProducts));
  }, [dispatch]);

  const handleAddToCart = (product) => {
    dispatch(addItem({ product }));
  };

  const handleRemoveFromCart = (productId) => {
    dispatch(removeItem(productId));
  };

  const handleUpdateQuantity = (productId, quantity) => {
    dispatch(updateQuantity({ productId, quantity }));
  };

  const handleApplyCoupon = (code) => {
    // 模拟优惠券验证
    const coupons = {
      'SAVE10': 10,
      'SAVE20': 20,
      'WELCOME': 15
    };
    
    const discount = coupons[code];
    if (discount) {
      dispatch(applyCoupon({ code, discount }));
    }
  };

  return (
    <div className="shopping-app">
      <ProductFilters 
        filters={filters}
        onChange={(filters) => dispatch(setProductFilters(filters))}
      />
      
      {loading ? (
        <div>Loading products...</div>
      ) : (
        <ProductGrid 
          products={products}
          onAddToCart={handleAddToCart}
        />
      )}
      
      <ShoppingCart 
        items={items}
        total={total}
        itemCount={itemCount}
        onRemoveItem={handleRemoveFromCart}
        onUpdateQuantity={handleUpdateQuantity}
        onApplyCoupon={handleApplyCoupon}
      />
    </div>
  );
};

const ProductFilters = ({ filters, onChange }) => {
  return (
    <div className="product-filters">
      <input
        type="text"
        placeholder="Search products..."
        value={filters.search}
        onChange={(e) => onChange({ search: e.target.value })}
      />
      
      <select
        value={filters.category}
        onChange={(e) => onChange({ category: e.target.value })}
      >
        <option value="all">All Categories</option>
        <option value="Electronics">Electronics</option>
        <option value="Books">Books</option>
        <option value="Home">Home</option>
      </select>
      
      <select
        value={filters.sortBy}
        onChange={(e) => onChange({ sortBy: e.target.value })}
      >
        <option value="name">Name</option>
        <option value="price">Price</option>
        <option value="category">Category</option>
      </select>
    </div>
  );
};

const ProductGrid = ({ products, onAddToCart }) => {
  return (
    <div className="product-grid">
      {products.map(product => (
        <ProductCard 
          key={product.id} 
          product={product} 
          onAddToCart={onAddToCart}
        />
      ))}
    </div>
  );
};

const ProductCard = ({ product, onAddToCart }) => {
  return (
    <div className="product-card">
      <h4>{product.name}</h4>
      <p className="category">{product.category}</p>
      <p className="price">${product.price.toFixed(2)}</p>
      <p className="stock">
        {product.inStock ? 'In Stock' : 'Out of Stock'}
      </p>
      <button 
        onClick={() => onAddToCart(product)}
        disabled={!product.inStock}
      >
        {product.inStock ? 'Add to Cart' : 'Out of Stock'}
      </button>
    </div>
  );
};

const ShoppingCart = ({ 
  items, 
  total, 
  itemCount, 
  onRemoveItem, 
  onUpdateQuantity,
  onApplyCoupon 
}) => {
  const [couponCode, setCouponCode] = useState('');

  return (
    <div className="shopping-cart">
      <h3>Shopping Cart ({itemCount} items)</h3>
      
      {items.length === 0 ? (
        <p>Your cart is empty</p>
      ) : (
        <>
          <div className="cart-items">
            {items.map(item => (
              <CartItem
                key={item.id}
                item={item}
                onRemove={onRemoveItem}
                onUpdateQuantity={onUpdateQuantity}
              />
            ))}
          </div>
          
          <div className="coupon-section">
            <input
              type="text"
              placeholder="Enter coupon code"
              value={couponCode}
              onChange={(e) => setCouponCode(e.target.value)}
            />
            <button onClick={() => onApplyCoupon(couponCode)}>
              Apply Coupon
            </button>
          </div>
          
          <div className="cart-summary">
            <div className="total">Total: ${total.toFixed(2)}</div>
            <button className="checkout-btn">Checkout</button>
          </div>
        </>
      )}
    </div>
  );
};

const CartItem = ({ item, onRemove, onUpdateQuantity }) => {
  return (
    <div className="cart-item">
      <div className="item-info">
        <h4>{item.name}</h4>
        <p className="price">${item.price.toFixed(2)}</p>
      </div>
      
      <div className="quantity-controls">
        <button 
          onClick={() => onUpdateQuantity(item.id, item.quantity - 1)}
        >
          -
        </button>
        <span>{item.quantity}</span>
        <button 
          onClick={() => onUpdateQuantity(item.id, item.quantity + 1)}
        >
          +
        </button>
      </div>
      
      <div className="item-total">
        ${(item.price * item.quantity).toFixed(2)}
      </div>
      
      <button 
        onClick={() => onRemove(item.id)}
        className="remove-btn"
      >
        ×
      </button>
    </div>
  );
};

通过 Redux Toolkit 和 React-Redux 的集成,你可以构建出强大、可预测的状态管理系统,同时享受现代化的开发体验和强大的开发者工具支持。

4.4 状态管理最佳实践与模式

4.4.1 状态设计原则

单一数据源与不可变性:

// 1. 状态结构设计原则

// ✅ 好的状态结构 - 扁平化、规范化
const goodStateStructure = {
  entities: {
    users: {
      byId: {
        1: { id: 1, name: 'Alice', email: 'alice@example.com' },
        2: { id: 2, name: 'Bob', email: 'bob@example.com' }
      },
      allIds: [1, 2]
    },
    posts: {
      byId: {
        101: { id: 101, title: 'Hello', authorId: 1 },
        102: { id: 102, title: 'World', authorId: 2 }
      },
      allIds: [101, 102]
    }
  },
  ui: {
    users: {
      loading: false,
      error: null,
      selectedUserId: 1,
      filters: { search: '', role: 'all' }
    },
    posts: {
      loading: true,
      error: null,
      selectedPostId: null,
      filters: { status: 'all' }
    }
  },
  auth: {
    user: 1,
    token: 'jwt-token',
    isAuthenticated: true
  }
};

// ❌ 避免的状态结构 - 深度嵌套、冗余
const badStateStructure = {
  users: [
    {
      id: 1,
      name: 'Alice',
      email: 'alice@example.com',
      posts: [
        { id: 101, title: 'Hello' },
        { id: 102, title: 'World' }
      ]
    }
  ]
};

// 2. 不可变性最佳实践
const createImmutableReducer = () => {
  return (state = initialState, action) => {
    switch (action.type) {
      case 'ADD_USER':
        // ✅ 正确的不可变更新
        return {
          ...state,
          entities: {
            ...state.entities,
            users: {
              ...state.entities.users,
              byId: {
                ...state.entities.users.byId,
                [action.user.id]: action.user
              },
              allIds: [...state.entities.users.allIds, action.user.id]
            }
          }
        };
        
      case 'UPDATE_USER':
        // ✅ 嵌套对象的正确更新
        return {
          ...state,
          entities: {
            ...state.entities,
            users: {
              ...state.entities.users,
              byId: {
                ...state.entities.users.byId,
                [action.userId]: {
                  ...state.entities.users.byId[action.userId],
                  ...action.updates
                }
              }
            }
          }
        };
        
      case 'DELETE_USER':
        // ✅ 正确的删除操作
        const { [action.userId]: deletedUser, ...remainingUsers } = state.entities.users.byId;
        return {
          ...state,
          entities: {
            ...state.entities,
            users: {
              byId: remainingUsers,
              allIds: state.entities.users.allIds.filter(id => id !== action.userId)
            }
          }
        };
        
      default:
        return state;
    }
  };
};

// 3. 使用 Immer 简化不可变操作
import { produce } from 'immer';

const createImmerReducer = () => {
  return produce((draft, action) => {
    switch (action.type) {
      case 'ADD_USER':
        draft.entities.users.byId[action.user.id] = action.user;
        draft.entities.users.allIds.push(action.user.id);
        break;
        
      case 'UPDATE_USER':
        Object.assign(draft.entities.users.byId[action.userId], action.updates);
        break;
        
      case 'DELETE_USER':
        delete draft.entities.users.byId[action.userId];
        draft.entities.users.allIds = draft.entities.users.allIds.filter(id => id !== action.userId);
        break;
        
      case 'SET_LOADING':
        draft.ui.users.loading = action.loading;
        break;
    }
  }, initialState);
};

4.4.2 性能优化策略

状态选择器优化:

import { createSelector } from 'reselect';

// 1. 基础选择器
const selectUsers = state => state.entities.users.byId;
const selectUserIds = state => state.entities.users.allIds;
const selectUsersLoading = state => state.ui.users.loading;
const selectUsersFilters = state => state.ui.users.filters;
const selectAuthUser = state => state.auth.user;

// 2. 记忆化选择器
export const selectAllUsers = createSelector(
  [selectUsers, selectUserIds],
  (usersById, userIds) => userIds.map(id => usersById[id])
);

export const selectActiveUsers = createSelector(
  [selectAllUsers],
  (users) => users.filter(user => user.status === 'active')
);

export const selectFilteredUsers = createSelector(
  [selectAllUsers, selectUsersFilters],
  (users, filters) => {
    return users.filter(user => {
      if (filters.search && !user.name.toLowerCase().includes(filters.search.toLowerCase())) {
        return false;
      }
      if (filters.role !== 'all' && user.role !== filters.role) {
        return false;
      }
      return true;
    });
  }
);

export const selectUsersWithPosts = createSelector(
  [selectAllUsers, state => state.entities.posts.byId],
  (users, postsById) => {
    return users.map(user => ({
      ...user,
      posts: user.postIds ? user.postIds.map(postId => postsById[postId]).filter(Boolean) : []
    }));
  }
);

export const selectUserStatistics = createSelector(
  [selectAllUsers, selectAllUsers],
  (allUsers, activeUsers) => ({
    total: allUsers.length,
    active: activeUsers.length,
    inactive: allUsers.length - activeUsers.length,
    percentage: ((activeUsers.length / allUsers.length) * 100).toFixed(1)
  })
);

// 3. 动态选择器工厂
export const selectUserById = (userId) => createSelector(
  [selectUsers],
  (users) => users[userId]
);

export const selectUsersByRole = (role) => createSelector(
  [selectAllUsers],
  (users) => users.filter(user => user.role === role)
);

// 4. 组件中使用优化选择器
const UserListComponent = () => {
  const dispatch = useDispatch();
  
  // 使用优化的选择器
  const filteredUsers = useSelector(selectFilteredUsers);
  const statistics = useSelector(selectUserStatistics);
  const loading = useSelector(selectUsersLoading);
  const filters = useSelector(selectUsersFilters);
  
  // 避免不必要的渲染
  const memoizedHandleFilterChange = useCallback((filterType, value) => {
    dispatch(setFilters({ [filterType]: value }));
  }, [dispatch]);

  if (loading) {
    return <div>Loading users...</div>;
  }

  return (
    <div className="user-list">
      <UserStats statistics={statistics} />
      <UserFilters filters={filters} onChange={memoizedHandleFilterChange} />
      <UserTable users={filteredUsers} />
    </div>
  );
};

组件优化策略:

import React, { memo, useMemo, useCallback } from 'react';
import { useSelector, useDispatch } from 'react-redux';

// 1. 记忆化组件
const UserCard = memo(({ user, onSelect, onUpdate, onDelete }) => {
  const memoizedOnSelect = useCallback(() => {
    onSelect(user.id);
  }, [onSelect, user.id]);

  const memoizedOnUpdate = useCallback((updates) => {
    onUpdate(user.id, updates);
  }, [onUpdate, user.id]);

  const memoizedOnDelete = useCallback(() => {
    onDelete(user.id);
  }, [onDelete, user.id]);

  // 记忆化计算属性
  const statusColor = useMemo(() => {
    switch (user.status) {
      case 'active': return '#28a745';
      case 'inactive': return '#6c757d';
      case 'suspended': return '#dc3545';
      default: return '#6c757d';
    }
  }, [user.status]);

  const formattedJoinDate = useMemo(() => {
    return new Date(user.joinDate).toLocaleDateString();
  }, [user.joinDate]);

  return (
    <div className="user-card">
      <div className="user-header">
        <img src={user.avatar} alt={user.name} className="avatar" />
        <h3>{user.name}</h3>
        <span 
          className="status-indicator" 
          style={{ backgroundColor: statusColor }}
        >
          {user.status}
        </span>
      </div>
      
      <div className="user-info">
        <p>{user.email}</p>
        <p>Role: {user.role}</p>
        <p>Joined: {formattedJoinDate}</p>
      </div>
      
      <div className="user-actions">
        <button onClick={memoizedOnSelect}>View</button>
        <button onClick={memoizedOnUpdate}>Edit</button>
        <button onClick={memoizedOnDelete}>Delete</button>
      </div>
    </div>
  );
});

UserCard.displayName = 'UserCard';

// 2. 虚拟化长列表
const VirtualizedUserList = memo(({ users, onUserSelect, onUserUpdate, onUserDelete }) => {
  const [visibleRange, setVisibleRange] = useState({ start: 0, end: 20 });
  const itemHeight = 120;
  const containerRef = useRef();

  const visibleUsers = useMemo(() => {
    return users.slice(visibleRange.start, visibleRange.end);
  }, [users, visibleRange]);

  const handleScroll = useCallback((e) => {
    const scrollTop = e.target.scrollTop;
    const containerHeight = e.target.clientHeight;
    
    const start = Math.floor(scrollTop / itemHeight);
    const visibleCount = Math.ceil(containerHeight / itemHeight);
    const end = start + visibleCount + 5; // 缓冲区
    
    setVisibleRange({ start, end });
  }, []);

  return (
    <div 
      ref={containerRef}
      className="virtualized-list"
      style={{ height: '600px', overflow: 'auto' }}
      onScroll={handleScroll}
    >
      <div style={{ height: users.length * itemHeight, position: 'relative' }}>
        {visibleUsers.map((user, index) => (
          <div
            key={user.id}
            style={{
              position: 'absolute',
              top: (visibleRange.start + index) * itemHeight,
              height: itemHeight,
              width: '100%'
            }}
          >
            <UserCard
              user={user}
              onSelect={onUserSelect}
              onUpdate={onUserUpdate}
              onDelete={onUserDelete}
            />
          </div>
        ))}
      </div>
    </div>
  );
});

// 3. 组件分割与懒加载
const UserManagement = () => {
  const [activeTab, setActiveTab] = useState('list');
  
  const UserListTab = useMemo(() => React.lazy(() => import('./UserListTab')), []);
  const UserStatsTab = useMemo(() => React.lazy(() => import('./UserStatsTab')), []);
  const UserSettingsTab = useMemo(() => React.lazy(() => import('./UserSettingsTab')), []);

  return (
    <div className="user-management">
      <div className="tabs">
        <button 
          onClick={() => setActiveTab('list')}
          className={activeTab === 'list' ? 'active' : ''}
        >
          Users List
        </button>
        <button 
          onClick={() => setActiveTab('stats')}
          className={activeTab === 'stats' ? 'active' : ''}
        >
          Statistics
        </button>
        <button 
          onClick={() => setActiveTab('settings')}
          className={activeTab === 'settings' ? 'active' : ''}
        >
          Settings
        </button>
      </div>

      <React.Suspense fallback={<div>Loading...</div>}>
        {activeTab === 'list' && <UserListTab />}
        {activeTab === 'stats' && <UserStatsTab />}
        {activeTab === 'settings' && <UserSettingsTab />}
      </React.Suspense>
    </div>
  );
};

4.4.3 错误处理与调试

全局错误边界与状态恢复:

import React, { Component } from 'react';
import { useDispatch } from 'react-redux';
import { resetError } from './slices/errorSlice';

// 1. 全局错误边界
class GlobalErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null, 
      errorInfo: null,
      errorCount: 0
    };
  }

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    console.error('Global error caught:', error, errorInfo);
    
    this.setState({
      error,
      errorInfo,
      errorCount: this.state.errorCount + 1
    });

    // 发送错误报告
    this.reportError(error, errorInfo);
    
    // 尝试恢复状态
    this.attemptRecovery();
  }

  reportError = async (error, errorInfo) => {
    try {
      await fetch('/api/errors', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          error: error.toString(),
          stack: error.stack,
          componentStack: errorInfo.componentStack,
          timestamp: new Date().toISOString(),
          userAgent: navigator.userAgent,
          url: window.location.href
        })
      });
    } catch (reportError) {
      console.error('Failed to report error:', reportError);
    }
  };

  attemptRecovery = () => {
    // 尝试恢复应用的稳定状态
    const { dispatch } = this.props;
    
    // 重置各种错误状态
    dispatch(resetError());
    
    // 清理可能损坏的状态
    localStorage.clear();
    
    // 如果错误次数过多,建议用户刷新页面
    if (this.state.errorCount > 3) {
      this.setState({ suggestRefresh: true });
    }
  };

  handleRetry = () => {
    this.setState({ hasError: false, error: null, errorInfo: null });
  };

  handleRefresh = () => {
    window.location.reload();
  };

  render() {
    if (this.state.hasError) {
      if (this.state.suggestRefresh) {
        return (
          <div className="error-boundary critical">
            <h2>Something went wrong</h2>
            <p>The application has encountered multiple errors.</p>
            <p>Please refresh the page to continue.</p>
            <button onClick={this.handleRefresh}>
              Refresh Page
            </button>
            <details style={{ marginTop: '20px' }}>
              <summary>Error Details</summary>
              <pre>{this.state.error && this.state.error.toString()}</pre>
              <pre>{this.state.errorInfo.componentStack}</pre>
            </details>
          </div>
        );
      }

      return (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <p>We're sorry, but something unexpected happened.</p>
          <div className="error-actions">
            <button onClick={this.handleRetry}>
              Try Again
            </button>
            <button onClick={this.handleRefresh}>
              Refresh Page
            </button>
          </div>
          
          <details style={{ marginTop: '20px' }}>
            <summary>Technical Details</summary>
            <pre>{this.state.error && this.state.error.toString()}</pre>
            <pre>{this.state.errorInfo.componentStack}</pre>
          </details>
        </div>
      );
    }

    return this.props.children;
  }
}

// 2. 错误状态管理 Slice
const errorSlice = createSlice({
  name: 'error',
  initialState: {
    errors: [],
    globalError: null,
    recoveryAttempts: 0
  },
  reducers: {
    addError: (state, action) => {
      const error = {
        id: Date.now(),
        timestamp: new Date().toISOString(),
        ...action.payload
      };
      state.errors.push(error);
      
      // 保持最近50个错误
      if (state.errors.length > 50) {
        state.errors = state.errors.slice(-50);
      }
    },
    removeError: (state, action) => {
      state.errors = state.errors.filter(error => error.id !== action.payload);
    },
    setGlobalError: (state, action) => {
      state.globalError = {
        ...action.payload,
        timestamp: new Date().toISOString()
      };
    },
    clearGlobalError: (state) => {
      state.globalError = null;
    },
    resetError: (state) => {
      state.errors = [];
      state.globalError = null;
    },
    incrementRecoveryAttempts: (state) => {
      state.recoveryAttempts += 1;
    },
    resetRecoveryAttempts: (state) => {
      state.recoveryAttempts = 0;
    }
  }
});

export const {
  addError,
  removeError,
  setGlobalError,
  clearGlobalError,
  resetError,
  incrementRecoveryAttempts,
  resetRecoveryAttempts
} = errorSlice.actions;

export default errorSlice.reducer;

// 3. 错误处理中间件
const errorHandlerMiddleware = (store) => (next) => (action) => {
  try {
    return next(action);
  } catch (error) {
    console.error('Redux action error:', error, action);
    
    store.dispatch(addError({
      type: 'REDUX_ACTION_ERROR',
      action: action.type,
      error: error.toString(),
      stack: error.stack
    }));
    
    // 可以根据错误类型进行不同的处理
    if (error.type === 'NETWORK_ERROR') {
      store.dispatch(setGlobalError({
        message: 'Network connection failed. Please check your internet connection.',
        type: 'error'
      }));
    }
    
    return action;
  }
};

// 4. 调试工具配置
const configureStoreWithDevTools = () => {
  const store = configureStore({
    reducer: {
      // ... 其他 reducers
      error: errorSlice
    },
    middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        serializableCheck: {
          // 忽略非序列化值的检查(用于调试)
          ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
          ignoredPaths: ['error.errors'] // 错误对象可能包含非序列化数据
        }
      }).concat(errorHandlerMiddleware),
    devTools: {
      name: 'My React App',
      trace: true,
      traceLimit: 25
    }
  });

  // 热重载支持
  if (process.env.NODE_ENV === 'development') {
    if (module.hot) {
      module.hot.accept('./rootReducer', () => {
        const newRootReducer = require('./rootReducer').default;
        store.replaceReducer(newRootReducer);
      });
    }
  }

  return store;
};

// 5. 状态恢复工具
const StateRecovery = () => {
  const dispatch = useDispatch();
  const { errors, globalError, recoveryAttempts } = useSelector(state => state.error);

  const attemptStateRecovery = useCallback(() => {
    dispatch(incrementRecoveryAttempts());
    
    // 清理可能损坏的状态
    dispatch(resetError());
    
    // 重新初始化关键状态
    dispatch({ type: 'RESET_APPLICATION_STATE' });
    
    // 记录恢复尝试
    console.log('State recovery attempt:', recoveryAttempts + 1);
  }, [dispatch, recoveryAttempts]);

  const clearErrors = useCallback(() => {
    dispatch(resetError());
    dispatch(resetRecoveryAttempts());
  }, [dispatch]);

  if (errors.length === 0 && !globalError) {
    return null;
  }

  return (
    <div className="state-recovery">
      {globalError && (
        <div className="global-error">
          <h3>Global Error</h3>
          <p>{globalError.message}</p>
          <button onClick={attemptStateRecovery}>
            Attempt Recovery
          </button>
          <button onClick={() => dispatch(clearGlobalError())}>
            Dismiss
          </button>
        </div>
      )}

      {errors.length > 0 && (
        <div className="error-log">
          <h4>Recent Errors ({errors.length})</h4>
          <ul>
            {errors.slice(-5).map(error => (
              <li key={error.id}>
                <small>{error.timestamp}</small>
                <p>{error.message}</p>
                <button onClick={() => dispatch(removeError(error.id))}>
                  Clear
                </button>
              </li>
            ))}
          </ul>
          
          <div className="recovery-actions">
            <button onClick={attemptStateRecovery}>
              Recovery (Attempt {recoveryAttempts})
            </button>
            <button onClick={clearErrors}>
              Clear All Errors
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

4.4.4 测试策略

状态管理测试:

import { configureStore } from '@reduxjs/toolkit';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { Provider } from 'react-redux';
import userReducer, { 
  fetchUsers, 
  createUser, 
  updateUserLocal,
  selectFilteredUsers 
} from './slices/usersSlice';

// 1. Reducer 测试
describe('usersSlice', () => {
  const initialState = {
    users: [],
    loading: false,
    error: null,
    filters: { search: '', role: 'all', status: 'all' }
  };

  it('should return the initial state', () => {
    expect(userReducer(undefined, { type: 'unknown' })).toEqual(initialState);
  });

  it('should handle setCurrentUser', () => {
    const user = { id: 1, name: 'Test User' };
    const action = { type: 'users/setCurrentUser', payload: user };
    const state = userReducer(initialState, action);
    
    expect(state.currentUser).toEqual(user);
  });

  it('should handle updateUserLocal', () => {
    const stateWithUsers = {
      ...initialState,
      users: [{ id: 1, name: 'John', email: 'john@example.com' }]
    };
    
    const updates = { name: 'John Doe' };
    const action = { type: 'users/updateUserLocal', payload: { userId: 1, updates } };
    const state = userReducer(stateWithUsers, action);
    
    expect(state.users[0].name).toBe('John Doe');
    expect(state.users[0].email).toBe('john@example.com');
  });
});

// 2. Async Thunk 测试
describe('users async thunks', () => {
  let store;

  beforeEach(() => {
    store = configureStore({
      reducer: {
        users: userReducer
      }
    });
  });

  it('should handle fetchUsers.fulfilled', async () => {
    const mockUsers = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ];

    global.fetch = jest.fn(() =>
      Promise.resolve({
        ok: true,
        json: () => Promise.resolve(mockUsers)
      })
    );

    await store.dispatch(fetchUsers());

    const state = store.getState().users;
    expect(state.loading).toBe(false);
    expect(state.users).toEqual(mockUsers);
    expect(state.error).toBe(null);
  });

  it('should handle fetchUsers.rejected', async () => {
    global.fetch = jest.fn(() =>
      Promise.reject(new Error('Network error'))
    );

    await store.dispatch(fetchUsers());

    const state = store.getState().users;
    expect(state.loading).toBe(false);
    expect(state.users).toEqual([]);
    expect(state.error).toBe('Network error');
  });

  it('should handle createUser.fulfilled', async () => {
    const newUser = { name: 'New User', email: 'new@example.com' };
    const createdUser = { id: 3, ...newUser };

    global.fetch = jest.fn(() =>
      Promise.resolve({
        ok: true,
        json: () => Promise.resolve(createdUser)
      })
    );

    await store.dispatch(createUser(newUser));

    const state = store.getState().users;
    expect(state.loading).toBe(false);
    expect(state.users).toContainEqual(createdUser);
    expect(state.currentUser).toEqual(createdUser);
  });
});

// 3. Selector 测试
describe('user selectors', () => {
  const mockState = {
    users: {
      users: [
        { id: 1, name: 'Alice', role: 'admin', status: 'active' },
        { id: 2, name: 'Bob', role: 'user', status: 'inactive' },
        { id: 3, name: 'Charlie', role: 'user', status: 'active' }
      ],
      filters: { search: '', role: 'all', status: 'all' }
    }
  };

  it('selectFilteredUsers should return all users when no filters applied', () => {
    const result = selectFilteredUsers(mockState);
    expect(result).toHaveLength(3);
  });

  it('selectFilteredUsers should filter by search term', () => {
    const stateWithSearchFilter = {
      ...mockState,
      users: {
        ...mockState.users,
        filters: { ...mockState.users.filters, search: 'Alice' }
      }
    };
    
    const result = selectFilteredUsers(stateWithSearchFilter);
    expect(result).toHaveLength(1);
    expect(result[0].name).toBe('Alice');
  });

  it('selectFilteredUsers should filter by role', () => {
    const stateWithRoleFilter = {
      ...mockState,
      users: {
        ...mockState.users,
        filters: { ...mockState.users.filters, role: 'admin' }
      }
    };
    
    const result = selectFilteredUsers(stateWithRoleFilter);
    expect(result).toHaveLength(1);
    expect(result[0].role).toBe('admin');
  });
});

// 4. 组件集成测试
const TestComponent = ({ userId }) => {
  const { users, loading, error, fetchUsers } = useUsers();
  
  useEffect(() => {
    fetchUsers();
  }, [fetchUsers]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <div>
      {users.map(user => (
        <div key={user.id} data-testid={`user-${user.id}`}>
          {user.name}
        </div>
      ))}
    </div>
  );
};

const renderWithProvider = (component, initialState = {}) => {
  const store = configureStore({
    reducer: {
      users: userReducer
    },
    preloadedState: initialState
  });

  return render(
    <Provider store={store}>
      {component}
    </Provider>
  );
};

describe('UserManagement component', () => {
  beforeEach(() => {
    global.fetch = jest.fn();
  });

  it('should render loading state initially', () => {
    renderWithProvider(<TestComponent />);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });

  it('should render users after successful fetch', async () => {
    const mockUsers = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ];

    global.fetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(mockUsers)
    });

    renderWithProvider(<TestComponent />);

    await waitFor(() => {
      expect(screen.getByTestId('user-1')).toBeInTheDocument();
      expect(screen.getByTestId('user-2')).toBeInTheDocument();
    });
  });

  it('should render error message on fetch failure', async () => {
    global.fetch.mockRejectedValueOnce(new Error('Network error'));

    renderWithProvider(<TestComponent />);

    await waitFor(() => {
      expect(screen.getByText('Error: Network error')).toBeInTheDocument();
    });
  });
});

// 5. 端到端状态流测试
describe('User Management E2E Flow', () => {
  let store;

  beforeEach(() => {
    store = configureStore({
      reducer: {
        users: userReducer
      }
    });

    global.fetch = jest.fn();
  });

  it('should complete full user management workflow', async () => {
    // 1. 初始状态
    let state = store.getState().users;
    expect(state.users).toHaveLength(0);
    expect(state.loading).toBe(false);

    // 2. 创建用户
    const newUser = { name: 'Test User', email: 'test@example.com' };
    const createdUser = { id: 1, ...newUser };

    global.fetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(createdUser)
    });

    const createResult = await store.dispatch(createUser(newUser));
    expect(createResult.meta.requestStatus).toBe('fulfilled');

    // 3. 验证用户创建
    state = store.getState().users;
    expect(state.users).toHaveLength(1);
    expect(state.users[0]).toEqual(createdUser);

    // 4. 更新用户
    const updates = { name: 'Updated User' };
    store.dispatch(updateUserLocal({ userId: 1, updates }));

    // 5. 验证用户更新
    state = store.getState().users;
    expect(state.users[0].name).toBe('Updated User');
    expect(state.users[0].email).toBe('test@example.com'); // 其他字段保持不变

    // 6. 测试过滤
    store.dispatch(setFilters({ search: 'Updated' }));
    state = store.getState().users;
    
    // 使用 selector 验证过滤结果
    const filteredUsers = selectFilteredUsers(state);
    expect(filteredUsers).toHaveLength(1);
    expect(filteredUsers[0].name).toBe('Updated User');
  });
});

通过这些最佳实践和模式,你可以构建出健壮、可测试、高性能的 React 状态管理系统。关键是要根据应用的具体需求选择合适的状态管理方案,并始终关注可维护性和开发体验。

posted @ 2025-11-29 18:16  seven3306  阅读(2)  评论(0)    收藏  举报