React状态管理与useState

状态(State)是React组件的"记忆",它使组件能够动态响应数据变化并重新渲染。本章将深入讲解如何使用useState Hook管理组件状态。

什么是状态(State)?

状态是组件内部的可变数据,当状态变化时,组件会自动重新渲染以反映最新的数据。

Props(属性)
  • 从父组件传递而来
  • 在组件内部是只读的
  • 用于组件间通信
  • 变化由父组件控制
State(状态)
  • 组件内部维护
  • 组件内部可修改
  • 用于组件内部状态
  • 变化触发重新渲染
历史背景:从Class组件到Hooks

在React 16.8之前,状态管理只能通过Class组件实现。Hooks的引入使得函数组件也能拥有状态管理能力,现在成为推荐的方式。

useState Hook基础

useState是React提供的一个Hook,用于在函数组件中添加状态管理。

useState基本语法


import React, { useState } from 'react';

function Example() {
  // 声明一个名为count的state变量,初始值为0
  // setCount是更新count的函数
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>当前计数: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        点击增加
      </button>
    </div>
  );
}
                        

useState的返回值

部分 名称 作用 说明
count 状态变量 存储当前状态值 组件重新渲染时会保持最新的值
setCount 更新函数 更新状态值并触发重新渲染 调用此函数时,组件会重新渲染

状态更新流程

1
初始化状态

useState(initialValue)设置初始状态值

2
触发更新

用户交互或事件触发setState(newValue)调用

3
状态更新

React更新内部状态值,并计划重新渲染

4
组件重新渲染

组件函数重新执行,返回新的JSX,界面更新

5
界面更新完成

用户看到更新后的界面,新的状态值被使用

计数器完整示例

计数器演示

0

import React, { useState } from 'react';

function Counter() {
  // 声明count状态变量,初始值为0
  const [count, setCount] = useState(0);

  // 增加计数器
  const increment = () => {
    setCount(count + 1);
  };

  // 减少计数器
  const decrement = () => {
    setCount(count - 1);
  };

  // 重置计数器
  const reset = () => {
    setCount(0);
  };

  // 翻倍计数器
  const double = () => {
    setCount(count * 2);
  };

  return (
    <div className="counter">
      <h2>计数器演示</h2>
      <div className="counter-value">{count}</div>
      <div className="counter-controls">
        <button onClick={decrement} className="btn btn-danger">
          <i className="fas fa-minus"></i> 减少
        </button>
        <button onClick={reset} className="btn btn-secondary">
          <i className="fas fa-redo"></i> 重置
        </button>
        <button onClick={increment} className="btn btn-success">
          <i className="fas fa-plus"></i> 增加
        </button>
      </div>
      <button onClick={double} className="btn btn-outline-primary">
        <i className="fas fa-times"></i> 翻倍
      </button>
    </div>
  );
}
                            

不同类型的状态管理

1. 基础类型状态


function BasicState() {
  // 字符串状态
  const [name, setName] = useState('React');

  // 数字状态
  const [age, setAge] = useState(18);

  // 布尔状态
  const [isActive, setIsActive] = useState(true);

  // 数组状态
  const [items, setItems] = useState(['苹果', '香蕉', '橙子']);

  return (
    <div>
      <p>名称: {name}</p>
      <p>年龄: {age}</p>
      <p>状态: {isActive ? '活跃' : '非活跃'}</p>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}
                    

2. 对象类型状态


function UserProfile() {
  // 对象状态
  const [user, setUser] = useState({
    name: '张三',
    age: 28,
    email: 'zhangsan@example.com',
    isVerified: false
  });

  // 更新对象状态 - 正确方式(展开运算符)
  const updateEmail = (newEmail) => {
    setUser({
      ...user,          // 复制原有属性
      email: newEmail   // 更新email属性
    });
  };

  // 更新对象状态 - 错误方式(直接修改)
  const wrongUpdate = () => {
    // ❌ 错误:直接修改原对象
    user.age = 29;
    setUser(user); // 不会触发重新渲染!
  };

  // 切换验证状态
  const toggleVerification = () => {
    setUser({
      ...user,
      isVerified: !user.isVerified
    });
  };

  return (
    <div className="user-profile">
      <h3>用户信息</h3>
      <p>姓名: {user.name}</p>
      <p>年龄: {user.age}</p>
      <p>邮箱: {user.email}</p>
      <p>验证状态: {user.isVerified ? '✓ 已认证' : '未认证'}</p>
      <button onClick={() => updateEmail('new@example.com')}>
        更新邮箱
      </button>
      <button onClick={toggleVerification}>
        切换验证状态
      </button>
    </div>
  );
}
                    
重要提醒:不可变性(Immutability)

React状态更新必须保持不可变性,即不直接修改原状态,而是创建新状态对象。这是因为React通过对比新旧状态来判断是否需要重新渲染。

错误: state.property = newValue

正确: setState({...state, property: newValue})

3. 数组类型状态


function TodoList() {
  const [todos, setTodos] = useState([
    { id: 1, text: '学习React', completed: true },
    { id: 2, text: '学习Hooks', completed: false },
    { id: 3, text: '构建项目', completed: false }
  ]);

  // 添加新的待办事项
  const addTodo = () => {
    const newTodo = {
      id: Date.now(),
      text: '新任务',
      completed: false
    };
    setTodos([...todos, newTodo]); // 创建新数组
  };

  // 删除待办事项
  const deleteTodo = (id) => {
    setTodos(todos.filter(todo => todo.id !== id)); // 过滤出剩余项
  };

  // 切换完成状态
  const toggleComplete = (id) => {
    setTodos(
      todos.map(todo =>
        todo.id === id
          ? { ...todo, completed: !todo.completed } // 创建新对象
          : todo
      )
    );
  };

  return (
    <div className="todo-list">
      <h3>待办事项 ({todos.length})</h3>
      <button onClick={addTodo} className="btn btn-primary">
        添加任务
      </button>
      <ul>
        {todos.map(todo => (
          <li key={todo.id} className={todo.completed ? 'completed' : ''}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleComplete(todo.id)}
            />
            <span>{todo.text}</span>
            <button
              onClick={() => deleteTodo(todo.id)}
              className="btn btn-sm btn-danger"
            >
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}
                    

useState高级技巧

1. 函数式更新

当新状态依赖于旧状态时,应该使用函数式更新,避免状态更新不同步的问题。


function CounterAdvanced() {
  const [count, setCount] = useState(0);

  // ❌ 可能有问题的方式(快速点击时)
  const incrementTwice = () => {
    setCount(count + 1); // 第一次更新
    setCount(count + 1); // 仍然使用旧的count值
  };

  // ✅ 正确的方式:使用函数式更新
  const incrementTwiceCorrectly = () => {
    setCount(prevCount => prevCount + 1);
    setCount(prevCount => prevCount + 1); // 基于最新状态
  };

  // 复杂计算示例
  const complexUpdate = () => {
    setCount(prev => {
      // 基于前一个值进行计算
      const newValue = prev * 2 + 5;
      console.log(`从 ${prev} 更新到 ${newValue}`);
      return newValue;
    });
  };

  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={incrementTwice}>
        增加两次(有问题)
      </button>
      <button onClick={incrementTwiceCorrectly}>
        增加两次(正确)
      </button>
      <button onClick={complexUpdate}>
        复杂更新
      </button>
    </div>
  );
}
                    

2. 惰性初始状态

如果初始状态需要通过复杂计算得到,可以使用函数作为useState的参数。


function ExpensiveComponent() {
  // ❌ 每次渲染都会执行复杂计算
  // const [data, setData] = useState(expensiveCalculation());

  // ✅ 只有初始渲染时执行一次
  const [data, setData] = useState(() => {
    // 这个函数只在组件首次渲染时执行
    console.log('执行复杂计算...');
    return expensiveCalculation();
  });

  // 模拟复杂计算
  function expensiveCalculation() {
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += i % 2;
    }
    return result;
  }

  return <div>计算结果: {data}</div>
}
                    

3. 多个状态变量


function UserForm() {
  // 分开的状态变量 - 推荐
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  const [isSubscribed, setIsSubscribed] = useState(false);

  // 合并的状态变量 - 适用于相关状态
  const [formData, setFormData] = useState({
    name: '',
    email: '',
    age: 0,
    isSubscribed: false
  });

  // 处理表单提交
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('表单数据:', { name, email, age, isSubscribed });
  };

  // 重置表单(分开状态)
  const resetForm = () => {
    setName('');
    setEmail('');
    setAge(0);
    setIsSubscribed(false);
  };

  return (
    <form onSubmit={handleSubmit} className="user-form">
      <div className="form-group">
        <label>姓名:</label>
        <input
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      <div className="form-group">
        <label>邮箱:</label>
        <input
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <div className="form-group">
        <label>年龄:</label>
        <input
          type="number"
          value={age}
          onChange={(e) => setAge(Number(e.target.value))}
        />
      </div>
      <div className="form-group">
        <label>
          <input
            type="checkbox"
            checked={isSubscribed}
            onChange={(e) => setIsSubscribed(e.target.checked)}
          />
          订阅新闻
        </label>
      </div>
      <button type="submit">提交</button>
      <button type="button" onClick={resetForm}>重置</button>
    </form>
  );
}
                    

常见问题与解决方案

React的状态更新是异步的,这意味着调用setState后,状态不会立即更新。React会批量处理多个状态更新以提高性能。


const [count, setCount] = useState(0);

// 错误:立即读取更新后的值
setCount(10);
console.log(count); // 仍然是0,不是10

// 正确:使用useEffect监听状态变化
useEffect(() => {
  console.log('count已更新:', count);
}, [count]);
                                    

使用函数式更新确保基于最新的状态值进行计算:


// 问题:快速点击时可能不会增加2
const incrementTwice = () => {
  setCount(count + 1);
  setCount(count + 1); // 仍然使用旧的count
};

// 解决方案:使用函数式更新
const incrementTwiceCorrectly = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1); // 基于最新的prev值
};
                                    

当多个组件需要共享同一状态时,应该将状态提升到它们最近的共同父组件中:


// 父组件管理共享状态
function Parent() {
  const [sharedState, setSharedState] = useState('共享数据');

  return (
    <div>
      <ChildA value={sharedState} onChange={setSharedState} />
      <ChildB value={sharedState} onChange={setSharedState} />
    </div>
  );
}
                                    

useState最佳实践

推荐做法
  • 使用有意义的变量名(如[user, setUser]
  • 复杂对象状态使用展开运算符更新
  • 相关状态可以合并到一个对象中
  • 使用函数式更新当新值依赖旧值时
  • 惰性初始化复杂初始状态
  • 保持状态不可变性
避免做法
  • 避免直接修改状态对象
  • 不要在循环中多次调用setState
  • 避免将不相关的状态放在一起
  • 不要在render中调用setState
  • 避免过度使用状态提升
  • 不要忘记处理异步更新

完整示例:购物车组件


import React, { useState } from 'react';

function ShoppingCart() {
  // 商品列表状态
  const [products, setProducts] = useState([
    { id: 1, name: 'iPhone 14', price: 6999, quantity: 1 },
    { id: 2, name: 'MacBook Pro', price: 12999, quantity: 1 },
    { id: 3, name: 'AirPods Pro', price: 1999, quantity: 2 }
  ]);

  // 折扣状态
  const [discount, setDiscount] = useState(0);
  const [hasDiscount, setHasDiscount] = useState(false);

  // 计算总价
  const calculateTotal = () => {
    const subtotal = products.reduce(
      (total, product) => total + product.price * product.quantity,
      0
    );
    return subtotal - discount;
  };

  // 更新商品数量
  const updateQuantity = (id, newQuantity) => {
    if (newQuantity < 1) return;

    setProducts(
      products.map(product =>
        product.id === id
          ? { ...product, quantity: newQuantity }
          : product
      )
    );
  };

  // 移除商品
  const removeProduct = (id) => {
    setProducts(products.filter(product => product.id !== id));
  };

  // 应用折扣
  const applyDiscount = (percentage) => {
    const subtotal = products.reduce(
      (total, product) => total + product.price * product.quantity,
      0
    );
    setDiscount(subtotal * (percentage / 100));
    setHasDiscount(true);
  };

  // 重置折扣
  const resetDiscount = () => {
    setDiscount(0);
    setHasDiscount(false);
  };

  return (
    <div className="shopping-cart">
      <h2>购物车</h2>

      <div className="cart-items">
        {products.map(product => (
          <div key={product.id} className="cart-item">
            <div className="item-info">
              <h4>{product.name}</h4>
              <p>单价: ¥{product.price.toFixed(2)}</p>
            </div>
            <div className="item-controls">
              <button
                onClick={() => updateQuantity(product.id, product.quantity - 1)}
                disabled={product.quantity <= 1}
              >
                -
              </button>
              <span>{product.quantity}</span>
              <button onClick={() => updateQuantity(product.id, product.quantity + 1)}>
                +
              </button>
              <button
                onClick={() => removeProduct(product.id)}
                className="btn-remove"
              >
                移除
              </button>
            </div>
            <div className="item-total">
              小计: ¥{(product.price * product.quantity).toFixed(2)}
            </div>
          </div>
        ))}
      </div>

      <div className="cart-summary">
        <div className="discount-controls">
          <button onClick={() => applyDiscount(10)}>9折优惠</button>
          <button onClick={() => applyDiscount(20)}>8折优惠</button>
          {hasDiscount && (
            <button onClick={resetDiscount}>取消折扣</button>
          )}
        </div>

        <div className="total-info">
          <p>商品总数: {products.reduce((sum, p) => sum + p.quantity, 0)}</p>
          {hasDiscount && <p>折扣金额: ¥{discount.toFixed(2)}</p>}
          <h3>总计: ¥{calculateTotal().toFixed(2)}</h3>
        </div>
      </div>
    </div>
  );
}
                    

本章要点总结

  • useState用于在函数组件中添加状态管理
  • 状态更新是异步的,使用函数式更新确保准确性
  • 保持状态不可变性,不直接修改原状态
  • 复杂初始状态使用惰性初始化
  • 相关状态可以合并,不相关状态分开管理
  • 状态提升用于共享状态的组件之间