React18+快速入门 - 1.响应式机制之 useState 和 useReducer

我将详细讲解 React 中两个核心状态管理 Hook:useStateuseReducer,从基础到高级用法。

一、useState 基础用法

1.1 基本概念

useState 是 React 中最基础的状态管理 Hook,用于在函数组件中添加状态。

jsx 复制代码
import React, { useState } from 'react';

function Counter() {
  // 基本用法:state 和 setState 函数
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加</button>
      <button onClick={() => setCount(count - 1)}>减少</button>
    </div>
  );
}

1.2 初始化方式

jsx 复制代码
// 方式1:直接值初始化
const [name, setName] = useState('张三');

// 方式2:函数初始化(惰性初始化,性能优化)
const [todos, setTodos] = useState(() => {
  const initialTodos = JSON.parse(localStorage.getItem('todos')) || [];
  return initialTodos;
});

// 方式3:对象状态
const [user, setUser] = useState({
  name: '张三',
  age: 25,
  email: 'zhangsan@example.com'
});

1.3 更新状态的不同方式

jsx 复制代码
function UserProfile() {
  const [user, setUser] = useState({
    name: '张三',
    age: 25,
    email: 'zhangsan@example.com'
  });
  
  const [count, setCount] = useState(0);
  
  // 1. 直接设置新值
  const updateName = () => {
    setUser({ ...user, name: '李四' });
  };
  
  // 2. 使用函数更新(基于前一个状态)
  const incrementTwice = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1); // 确保基于最新状态
  };
  
  // 3. 部分更新对象
  const updateAge = (newAge) => {
    setUser(prevUser => ({
      ...prevUser,
      age: newAge
    }));
  };
  
  // 4. 重置状态
  const resetUser = () => {
    setUser({
      name: '张三',
      age: 25,
      email: 'zhangsan@example.com'
    });
  };
  
  return (
    <div>
      <p>姓名: {user.name}</p>
      <p>年龄: {user.age}</p>
      <p>计数: {count}</p>
      <button onClick={updateName}>改名</button>
      <button onClick={() => updateAge(30)}>改年龄</button>
      <button onClick={incrementTwice}>增加两次</button>
      <button onClick={resetUser}>重置</button>
    </div>
  );
}

1.4 复杂状态管理示例

jsx 复制代码
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');
  
  // 添加 todo
  const addTodo = () => {
    if (!inputValue.trim()) return;
    
    const newTodo = {
      id: Date.now(),
      text: inputValue,
      completed: false
    };
    
    setTodos(prevTodos => [...prevTodos, newTodo]);
    setInputValue('');
  };
  
  // 切换完成状态
  const toggleTodo = (id) => {
    setTodos(prevTodos =>
      prevTodos.map(todo =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };
  
  // 删除 todo
  const deleteTodo = (id) => {
    setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
  };
  
  // 清空所有
  const clearAll = () => {
    setTodos([]);
  };
  
  return (
    <div>
      <h2>Todo List</h2>
      <div>
        <input
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="输入待办事项"
        />
        <button onClick={addTodo}>添加</button>
      </div>
      
      <ul>
        {todos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span style={{
              textDecoration: todo.completed ? 'line-through' : 'none'
            }}>
              {todo.text}
            </span>
            <button onClick={() => deleteTodo(todo.id)}>删除</button>
          </li>
        ))}
      </ul>
      
      <div>
        <p>总任务数: {todos.length}</p>
        <p>已完成: {todos.filter(t => t.completed).length}</p>
        <button onClick={clearAll}>清空所有</button>
      </div>
    </div>
  );
}

二、useReducer 基础用法

2.1 基本概念

useReducer 是更复杂的状态管理方案,适合状态逻辑复杂或包含多个子值的场景。

jsx 复制代码
import React, { useReducer } from 'react';

// 1. 定义 reducer 函数
function counterReducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { count: state.count + 1 };
    case 'DECREMENT':
      return { count: state.count - 1 };
    case 'RESET':
      return { count: 0 };
    case 'SET':
      return { count: action.payload };
    default:
      return state;
  }
}

function CounterWithReducer() {
  // 2. 使用 useReducer
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'INCREMENT' })}>增加</button>
      <button onClick={() => dispatch({ type: 'DECREMENT' })}>减少</button>
      <button onClick={() => dispatch({ type: 'RESET' })}>重置</button>
      <button onClick={() => dispatch({ type: 'SET', payload: 10 })}>
        设置为10
      </button>
    </div>
  );
}

2.2 Reducer 的三种写法

jsx 复制代码
// 写法1:switch 语句(最常用)
function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
    case 'SET_FILTER':
      return {
        ...state,
        filter: action.payload
      };
    default:
      return state;
  }
}

// 写法2:对象查找(适合简单场景)
function calculatorReducer(state, action) {
  const handlers = {
    ADD: () => ({ result: state.result + action.payload }),
    SUBTRACT: () => ({ result: state.result - action.payload }),
    MULTIPLY: () => ({ result: state.result * action.payload }),
    DIVIDE: () => ({ result: state.result / action.payload }),
    RESET: () => ({ result: 0 })
  };
  
  return handlers[action.type] ? handlers[action.type]() : state;
}

// 写法3:函数工厂模式(可扩展性强)
function createReducer(initialState, handlers) {
  return function reducer(state = initialState, action) {
    if (handlers.hasOwnProperty(action.type)) {
      return handlers[action.type](state, action);
    }
    return state;
  };
}

// 使用工厂模式
const userReducer = createReducer(
  { name: '', age: 0, loading: false },
  {
    SET_NAME: (state, action) => ({ ...state, name: action.payload }),
    SET_AGE: (state, action) => ({ ...state, age: action.payload }),
    SET_LOADING: (state, action) => ({ ...state, loading: action.payload })
  }
);

2.3 复杂状态管理示例

jsx 复制代码
const initialState = {
  cartItems: [],
  totalPrice: 0,
  totalQuantity: 0,
  discount: 0,
  isLoading: false,
  error: null
};

function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM':
      const existingItem = state.cartItems.find(
        item => item.id === action.payload.id
      );
      
      let updatedItems;
      if (existingItem) {
        updatedItems = state.cartItems.map(item =>
          item.id === action.payload.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      } else {
        updatedItems = [...state.cartItems, { ...action.payload, quantity: 1 }];
      }
      
      return {
        ...state,
        cartItems: updatedItems,
        totalQuantity: state.totalQuantity + 1,
        totalPrice: state.totalPrice + action.payload.price
      };
      
    case 'REMOVE_ITEM':
      const itemToRemove = state.cartItems.find(
        item => item.id === action.payload
      );
      
      if (!itemToRemove) return state;
      
      const filteredItems = state.cartItems.filter(
        item => item.id !== action.payload
      );
      
      return {
        ...state,
        cartItems: filteredItems,
        totalQuantity: state.totalQuantity - itemToRemove.quantity,
        totalPrice: state.totalPrice - (itemToRemove.price * itemToRemove.quantity)
      };
      
    case 'UPDATE_QUANTITY':
      const { id, quantity } = action.payload;
      const itemToUpdate = state.cartItems.find(item => item.id === id);
      
      if (!itemToUpdate || quantity < 1) return state;
      
      const quantityDiff = quantity - itemToUpdate.quantity;
      const updatedCartItems = state.cartItems.map(item =>
        item.id === id ? { ...item, quantity } : item
      );
      
      return {
        ...state,
        cartItems: updatedCartItems,
        totalQuantity: state.totalQuantity + quantityDiff,
        totalPrice: state.totalPrice + (itemToUpdate.price * quantityDiff)
      };
      
    case 'APPLY_DISCOUNT':
      return {
        ...state,
        discount: action.payload
      };
      
    case 'CLEAR_CART':
      return {
        ...initialState
      };
      
    case 'SET_LOADING':
      return {
        ...state,
        isLoading: action.payload
      };
      
    case 'SET_ERROR':
      return {
        ...state,
        error: action.payload,
        isLoading: false
      };
      
    default:
      return state;
  }
}

function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, initialState);
  
  const addItem = (item) => {
    dispatch({ type: 'ADD_ITEM', payload: item });
  };
  
  const removeItem = (id) => {
    dispatch({ type: 'REMOVE_ITEM', payload: id });
  };
  
  const updateQuantity = (id, quantity) => {
    dispatch({ type: 'UPDATE_QUANTITY', payload: { id, quantity } });
  };
  
  const applyDiscount = (percentage) => {
    dispatch({ type: 'APPLY_DISCOUNT', payload: percentage });
  };
  
  const clearCart = () => {
    dispatch({ type: 'CLEAR_CART' });
  };
  
  // 计算最终价格
  const finalPrice = state.totalPrice * (1 - state.discount / 100);
  
  return (
    <div>
      <h2>购物车</h2>
      
      {state.isLoading && <p>加载中...</p>}
      {state.error && <p style={{ color: 'red' }}>错误: {state.error}</p>}
      
      <div>
        <h3>商品列表</h3>
        <button onClick={() => addItem({ id: 1, name: '商品A', price: 100 })}>
          添加商品A
        </button>
        <button onClick={() => addItem({ id: 2, name: '商品B', price: 200 })}>
          添加商品B
        </button>
      </div>
      
      <div>
        <h3>购物车内容</h3>
        {state.cartItems.length === 0 ? (
          <p>购物车为空</p>
        ) : (
          <ul>
            {state.cartItems.map(item => (
              <li key={item.id}>
                {item.name} - ¥{item.price} × {item.quantity}
                <button onClick={() => updateQuantity(item.id, item.quantity + 1)}>
                  +
                </button>
                <button onClick={() => updateQuantity(item.id, item.quantity - 1)}>
                  -
                </button>
                <button onClick={() => removeItem(item.id)}>删除</button>
              </li>
            ))}
          </ul>
        )}
      </div>
      
      <div>
        <h3>总计</h3>
        <p>商品数量: {state.totalQuantity}</p>
        <p>原价: ¥{state.totalPrice.toFixed(2)}</p>
        <p>折扣: {state.discount}%</p>
        <p>最终价格: ¥{finalPrice.toFixed(2)}</p>
      </div>
      
      <div>
        <button onClick={() => applyDiscount(10)}>应用10%折扣</button>
        <button onClick={() => applyDiscount(20)}>应用20%折扣</button>
        <button onClick={clearCart}>清空购物车</button>
      </div>
    </div>
  );
}

三、useState vs useReducer 选择指南

3.1 何时使用 useState

  • 简单状态:独立的状态值
  • 布尔值、字符串、数字:简单数据类型
  • 组件内部状态:不涉及复杂逻辑
  • 状态更新简单:直接设置新值
jsx 复制代码
// 适合 useState 的场景
const [isOpen, setIsOpen] = useState(false);
const [name, setName] = useState('');
const [count, setCount] = useState(0);
const [selectedItem, setSelectedItem] = useState(null);

3.2 何时使用 useReducer

  • 复杂状态逻辑:多个相互关联的状态值
  • 状态依赖前一个状态:需要基于前状态计算
  • 多个操作类型:不同的 action 类型
  • 状态更新逻辑复杂:需要集中管理更新逻辑
  • 需要可预测的状态更新:便于调试和测试
jsx 复制代码
// 适合 useReducer 的场景
const [formState, dispatch] = useReducer(formReducer, initialFormState);
const [gameState, gameDispatch] = useReducer(gameReducer, initialGameState);
const [cartState, cartDispatch] = useReducer(cartReducer, initialCartState);

3.3 对比示例

jsx 复制代码
// 使用 useState 实现表单
function FormWithUseState() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    password: '',
    confirmPassword: ''
  });
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: value
    }));
  };
  
  return (
    <form>
      <input
        name="username"
        value={formData.username}
        onChange={handleChange}
      />
      {/* 其他字段 */}
    </form>
  );
}

// 使用 useReducer 实现相同的表单
const formReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_FIELD':
      return {
        ...state,
        [action.field]: action.value
      };
    case 'RESET_FORM':
      return initialState;
    default:
      return state;
  }
};

function FormWithUseReducer() {
  const [state, dispatch] = useReducer(formReducer, initialState);
  
  const handleChange = (e) => {
    const { name, value } = e.target;
    dispatch({
      type: 'UPDATE_FIELD',
      field: name,
      value: value
    });
  };
  
  return (
    <form>
      <input
        name="username"
        value={state.username}
        onChange={handleChange}
      />
      {/* 其他字段 */}
    </form>
  );
}

四、高级技巧和最佳实践

4.1 自定义 Hook 封装

jsx 复制代码
// 自定义 useLocalStorage Hook
function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

// 使用自定义 Hook
function TodoApp() {
  const [todos, setTodos] = useLocalStorage('todos', []);
  // ... 其他逻辑
}

// 自定义 useReducer with Logger
function useReducerWithLogger(reducer, initialState) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  const dispatchWithLogger = (action) => {
    console.log('Previous State:', state);
    console.log('Action:', action);
    const nextState = reducer(state, action);
    console.log('Next State:', nextState);
    dispatch(action);
  };
  
  return [state, dispatchWithLogger];
}

4.2 性能优化

jsx 复制代码
function OptimizedComponent() {
  // 使用 useReducer 避免不必要的重新渲染
  const [state, dispatch] = useReducer(complexReducer, initialState);
  
  // 使用 useMemo 记忆化计算值
  const computedValue = useMemo(() => {
    return expensiveCalculation(state);
  }, [state]);
  
  // 使用 useCallback 记忆化函数
  const handleAction = useCallback((payload) => {
    dispatch({ type: 'ACTION_TYPE', payload });
  }, [dispatch]);
  
  return (
    <div>
      <ChildComponent onAction={handleAction} />
    </div>
  );
}

4.3 测试策略

jsx 复制代码
// reducer 的单元测试
describe('todoReducer', () => {
  const initialState = { todos: [] };
  
  test('should add a todo', () => {
    const action = {
      type: 'ADD_TODO',
      payload: { id: 1, text: 'Test todo', completed: false }
    };
    
    const newState = todoReducer(initialState, action);
    
    expect(newState.todos).toHaveLength(1);
    expect(newState.todos[0].text).toBe('Test todo');
  });
  
  test('should toggle a todo', () => {
    const state = {
      todos: [{ id: 1, text: 'Test todo', completed: false }]
    };
    
    const action = { type: 'TOGGLE_TODO', payload: 1 };
    const newState = todoReducer(state, action);
    
    expect(newState.todos[0].completed).toBe(true);
  });
});

// 组件测试
test('should update count when increment button is clicked', () => {
  render(<Counter />);
  const incrementButton = screen.getByText('增加');
  
  fireEvent.click(incrementButton);
  
  expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

五、实战项目:待办事项应用

jsx 复制代码
import React, { useReducer, useState } from 'react';

const initialState = {
  todos: [],
  filter: 'all', // 'all', 'active', 'completed'
  nextId: 1
};

function todoReducer(state, action) {
  switch (action.type) {
    case 'ADD_TODO':
      const newTodo = {
        id: state.nextId,
        text: action.payload,
        completed: false,
        createdAt: new Date().toISOString()
      };
      return {
        ...state,
        todos: [...state.todos, newTodo],
        nextId: state.nextId + 1
      };
      
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        )
      };
      
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
      
    case 'EDIT_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload.id
            ? { ...todo, text: action.payload.text }
            : todo
        )
      };
      
    case 'SET_FILTER':
      return {
        ...state,
        filter: action.payload
      };
      
    case 'CLEAR_COMPLETED':
      return {
        ...state,
        todos: state.todos.filter(todo => !todo.completed)
      };
      
    case 'TOGGLE_ALL':
      const allCompleted = state.todos.every(todo => todo.completed);
      return {
        ...state,
        todos: state.todos.map(todo => ({
          ...todo,
          completed: !allCompleted
        }))
      };
      
    default:
      return state;
  }
}

function TodoApp() {
  const [state, dispatch] = useReducer(todoReducer, initialState);
  const [inputValue, setInputValue] = useState('');
  const [editingId, setEditingId] = useState(null);
  const [editText, setEditText] = useState('');
  
  // 计算属性
  const filteredTodos = state.todos.filter(todo => {
    if (state.filter === 'active') return !todo.completed;
    if (state.filter === 'completed') return todo.completed;
    return true;
  });
  
  const activeCount = state.todos.filter(todo => !todo.completed).length;
  const completedCount = state.todos.length - activeCount;
  
  // 事件处理函数
  const handleAddTodo = (e) => {
    e.preventDefault();
    if (!inputValue.trim()) return;
    
    dispatch({ type: 'ADD_TODO', payload: inputValue });
    setInputValue('');
  };
  
  const handleEditStart = (id, text) => {
    setEditingId(id);
    setEditText(text);
  };
  
  const handleEditSave = (id) => {
    if (!editText.trim()) {
      dispatch({ type: 'DELETE_TODO', payload: id });
    } else {
      dispatch({ type: 'EDIT_TODO', payload: { id, text: editText } });
    }
    setEditingId(null);
    setEditText('');
  };
  
  const handleEditCancel = () => {
    setEditingId(null);
    setEditText('');
  };
  
  return (
    <div className="todo-app">
      <header>
        <h1>待办事项</h1>
        <form onSubmit={handleAddTodo}>
          <input
            type="text"
            value={inputValue}
            onChange={(e) => setInputValue(e.target.value)}
            placeholder="输入新的待办事项..."
            autoFocus
          />
          <button type="submit">添加</button>
        </form>
      </header>
      
      <main>
        {state.todos.length > 0 && (
          <div className="todo-controls">
            <button
              onClick={() => dispatch({ type: 'TOGGLE_ALL' })}
              className="toggle-all"
            >
              {activeCount === 0 ? '取消全选' : '全选'}
            </button>
            
            <div className="filters">
              <button
                onClick={() => dispatch({ type: 'SET_FILTER', payload: 'all' })}
                className={state.filter === 'all' ? 'active' : ''}
              >
                全部 ({state.todos.length})
              </button>
              <button
                onClick={() => dispatch({ type: 'SET_FILTER', payload: 'active' })}
                className={state.filter === 'active' ? 'active' : ''}
              >
                待办 ({activeCount})
              </button>
              <button
                onClick={() => dispatch({ type: 'SET_FILTER', payload: 'completed' })}
                className={state.filter === 'completed' ? 'active' : ''}
              >
                已完成 ({completedCount})
              </button>
            </div>
            
            {completedCount > 0 && (
              <button
                onClick={() => dispatch({ type: 'CLEAR_COMPLETED' })}
                className="clear-completed"
              >
                清除已完成
              </button>
            )}
          </div>
        )}
        
        <ul className="todo-list">
          {filteredTodos.map(todo => (
            <li
              key={todo.id}
              className={`todo-item ${todo.completed ? 'completed' : ''} ${editingId === todo.id ? 'editing' : ''}`}
            >
              {editingId === todo.id ? (
                <div className="edit-form">
                  <input
                    type="text"
                    value={editText}
                    onChange={(e) => setEditText(e.target.value)}
                    onBlur={() => handleEditSave(todo.id)}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') handleEditSave(todo.id);
                      if (e.key === 'Escape') handleEditCancel();
                    }}
                    autoFocus
                  />
                  <button onClick={() => handleEditSave(todo.id)}>保存</button>
                  <button onClick={handleEditCancel}>取消</button>
                </div>
              ) : (
                <>
                  <input
                    type="checkbox"
                    checked={todo.completed}
                    onChange={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
                    className="todo-checkbox"
                  />
                  <span
                    className="todo-text"
                    onDoubleClick={() => handleEditStart(todo.id, todo.text)}
                  >
                    {todo.text}
                  </span>
                  <div className="todo-actions">
                    <button
                      onClick={() => handleEditStart(todo.id, todo.text)}
                      className="edit-btn"
                    >
                      编辑
                    </button>
                    <button
                      onClick={() => dispatch({ type: 'DELETE_TODO', payload: todo.id })}
                      className="delete-btn"
                    >
                      删除
                    </button>
                  </div>
                </>
              )}
            </li>
          ))}
        </ul>
        
        {state.todos.length === 0 && (
          <div className="empty-state">
            <p>暂无待办事项</p>
            <p>开始添加你的第一个待办事项吧!</p>
          </div>
        )}
      </main>
      
      <footer>
        <p>待办事项: {activeCount} 个未完成</p>
        <p>当前过滤: {state.filter}</p>
      </footer>
    </div>
  );
}

// 添加样式
const styles = `
  .todo-app {
    max-width: 600px;
    margin: 0 auto;
    padding: 20px;
    font-family: Arial, sans-serif;
  }
  
  header {
    text-align: center;
    margin-bottom: 30px;
  }
  
  header form {
    display: flex;
    gap: 10px;
    margin-top: 20px;
  }
  
  header input {
    flex: 1;
    padding: 10px;
    font-size: 16px;
  }
  
  header button {
    padding: 10px 20px;
    background: #007bff;
    color: white;
    border: none;
    cursor: pointer;
  }
  
  .todo-controls {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 10px 0;
    border-bottom: 1px solid #eee;
    margin-bottom: 20px;
  }
  
  .filters {
    display: flex;
    gap: 10px;
  }
  
  .filters button.active {
    background: #007bff;
    color: white;
  }
  
  .todo-list {
    list-style: none;
    padding: 0;
  }
  
  .todo-item {
    display: flex;
    align-items: center;
    padding: 10px;
    border-bottom: 1px solid #eee;
  }
  
  .todo-item.completed .todo-text {
    text-decoration: line-through;
    color: #999;
  }
  
  .todo-checkbox {
    margin-right: 10px;
  }
  
  .todo-text {
    flex: 1;
    margin-right: 10px;
  }
  
  .todo-actions {
    display: flex;
    gap: 5px;
    opacity: 0;
    transition: opacity 0.2s;
  }
  
  .todo-item:hover .todo-actions {
    opacity: 1;
  }
  
  .edit-form {
    display: flex;
    width: 100%;
    gap: 10px;
  }
  
  .edit-form input {
    flex: 1;
    padding: 5px;
  }
  
  .empty-state {
    text-align: center;
    color: #999;
    padding: 40px;
  }
  
  footer {
    margin-top: 30px;
    padding-top: 20px;
    border-top: 1px solid #eee;
    color: #666;
    font-size: 14px;
  }
`;

// 在组件中添加样式
const StyledTodoApp = () => (
  <>
    <style>{styles}</style>
    <TodoApp />
  </>
);

export default StyledTodoApp;

六、总结

6.1 核心要点

  1. useState

    • 适合简单、独立的状态管理
    • 直接设置状态值或使用函数更新
    • 性能优化:惰性初始化和函数更新
  2. useReducer

    • 适合复杂状态逻辑和多个关联状态
    • 集中管理状态更新逻辑
    • 便于测试和调试
  3. 选择原则

    • 简单状态用 useState
    • 复杂状态用 useReducer
    • 考虑组件可维护性和可测试性

6.2 最佳实践

  1. 命名约定

    • 状态:名词(user, todos, isLoading
    • 更新函数:setXxxdispatch
    • Action:动词过去式(ADD_TODO, UPDATE_USER
  2. 不可变性

    • 始终返回新状态对象
    • 不直接修改原状态
  3. 代码组织

    • 相关状态放在一起
    • 复杂 reducer 拆分为多个小函数
    • 自定义 Hook 封装重复逻辑
  4. 性能优化

    • 避免不必要的重新渲染
    • 使用 useMemouseCallback
    • 状态提升和状态下沉

6.3 下一步学习

  • useContext:全局状态管理
  • useEffect:副作用处理
  • 自定义 Hook:逻辑复用
  • 状态管理库:Redux、Zustand、Recoil

通过掌握 useStateuseReducer,你已经具备了 React 状态管理的核心能力。在实际开发中,根据具体场景选择合适的方法,会让你的代码更加清晰和可维护。

相关推荐
Access开发易登软件1 天前
Access 数据可视化:如何制作箱形图
前端·数据库·vba·access·access开发
Tiramisu20231 天前
【VUE】删除 node_modules几种高效方法
前端·javascript·vue.js
钰fly1 天前
Windows Forms开发工具与功能总结表
前端·c#
共享家95271 天前
测试常用函数(一)
java·前端·javascript
两个人的幸福online1 天前
给cocos 3.8 消息控制器
开发语言·javascript·ecmascript
林恒smileZAZ1 天前
vue对接海康摄像头-H5player
前端·javascript·vue.js
韩曙亮1 天前
【Web APIs】移动端返回顶部案例 ( 返回顶部按钮 显示 / 隐藏 设置 | 返回顶部操作 )
前端·javascript·css·html·移动端·web apis·返回顶部
Cache技术分享1 天前
279. Java Stream API - Stream 拼接的两种方式:concat() vs flatMap()
前端·后端
GDAL1 天前
Tailwind CSS 响应式设计实战指南:从零搭建书签篮自适应页面
前端·css·tailwindcss·书签篮
L-岁月染过的梦1 天前
前端使用JS实现端口探活
开发语言·前端·javascript