详细讲解 React 中两个最重要的状态管理工具: setState(用于类组件)和 useState Hook(用于函数组件)。

我会从概念、用法、异同点和最佳实践几个方面来阐述。


1. this.setState (用于类组件)

setState 是类组件中用于更新组件局部状态的方法。状态(state)是一个对象,存储着组件内部可能变化的数据。

基本用法
import React, { Component } from 'react';

class CounterClass extends Component {
  // 1. 在构造函数中初始化 state
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  // 也可以使用类属性简化初始化(需要 Babel 支持)
  // state = {
  //   count: 0
  // };

  increment = () => {
    // 2. 使用 setState 更新状态
    // 用法一:传入一个对象,它会浅合并到当前 state
    this.setState({ count: this.state.count + 1 });

    // 用法二:传入一个 updater 函数,用于依赖前一个状态
    // this.setState((prevState, prevProps) => {
    //   return { count: prevState.count + 1 };
    // });
  };

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times (Class)</p>
        <button onClick={this.increment}>Click me</button>
      </div>
    );
  }
}

export default CounterClass;
重要特性
  1. 异步性setState异步的。React 会将多个 setState 调用合并(Batching)在一起以提高性能,然后再进行一次重新渲染。这意味着你在调用 setState立即读取 this.state,得到的可能不是更新后的值。
  2. 函数式更新:当新状态依赖于前一个状态时(如计数器),必须使用函数形式 (this.setState((prevState) => {...})) 来保证你使用的是最新的 state。
  3. 回调函数setState 接受第二个参数,它是一个可选的回调函数,会在状态更新完毕且组件重新渲染后执行。
    this.setState({ count: 10 }, () => {
      console.log('State has been updated and component re-rendered:', this.state.count);
    });
    
  4. 浅合并:当你传入一个对象时,setState 会将它浅合并到当前的 state。你不需要担心会覆盖整个 state 对象。

2. useState Hook (用于函数组件)

useState 是 React 16.8 引入的 Hook,它让你在函数组件中添加状态。

基本用法
import React, { useState } from 'react';

function CounterFunction() {
  // 1. 调用 useState Hook 来声明一个状态变量。
  // 它返回一个数组: [当前的状态值, 一个用于更新它的函数]
  // useState 的参数是状态的初始值
  const [count, setCount] = useState(0);

  const increment = () => {
    // 2. 使用 setter 函数更新状态
    // 用法一:直接传入新的值
    setCount(count + 1);

    // 用法二:传入一个 updater 函数,用于依赖前一个状态
    // setCount(prevCount => prevCount + 1);
  };

  return (
    <div>
      <p>You clicked {count} times (Function)</p>
      <button onClick={increment}>Click me</button>
    </div>
  );
}

export default CounterFunction;
重要特性
  1. 异步性:由 useState 返回的 setter 函数(如 setCount)也是异步的。它们也会被 React 批量处理以优化性能。
  2. 函数式更新:与 setState 一样,当新状态依赖于旧状态时,必须使用函数形式 (setCount(prevCount => prevCount + 1))。
  3. 直接替换 vs 合并useState 的 setter 函数不会自动合并对象。如果你的状态是一个对象,你需要手动合并它。
    const [user, setUser] = useState({ name: 'John', age: 30 });
    
    // 错误:这会覆盖整个 state,只留下 `age`,`name` 会丢失!
    // setUser({ age: 31 });
    
    // 正确:使用展开运算符 (...) 先复制原有属性,再覆盖更新的属性
    setUser(prevUser => ({ ...prevUser, age: 31 }));
    
  4. 多个状态:你可以在一个组件中多次使用 useState 来管理不同的状态变量。这是推荐的做法,而不是像类组件那样把所有状态放在一个大对象里。

3. 核心异同点对比

特性 Class 组件 setState Function 组件 useState
声明状态 在构造函数或类属性中初始化一个 state 对象 调用 useState Hook,返回状态变量和 setter 函数
更新状态 this.setState(newStateOrUpdaterFn) setState(newValueOrUpdaterFn)
状态类型 通常是一个对象 可以是任何类型(数字、字符串、对象、数组等)
合并行为 自动浅合并对象 不自动合并,需要手动合并(使用展开运算符)
获取最新状态 使用回调函数作为 setState 的第二个参数 使用 useEffect Hook 来响应状态变化
异步性 异步,会批量处理 异步,会批量处理
函数式更新 this.setState((prevState) => newState) setState(prevValue => newValue)

4. 如何选择?最佳实践

  1. 使用函数组件和 Hooks:对于新项目和新组件,强烈推荐使用函数组件和 useState Hook。这是 React 现在的标准和未来发展方向。它代码更简洁,逻辑更复用。
  2. 使用函数式更新只要新状态依赖于旧状态,就一定要使用函数式更新。这是避免因异步更新导致 bug 的最重要规则。
    // ✅ 正确且安全
    setCount(prevCount => prevCount + 1);
    // ❌ 在某些情况下可能不安全(例如多次快速点击)
    setCount(count + 1);
    
  3. 处理对象和数组:使用 useState 更新对象或数组时,记得创建新的引用(使用展开运算符 ...Array.prototype.map/filter 等),以遵循不可变原则,确保 React 能正确检测到变化并触发重新渲染。
  4. 状态结构:在函数组件中,如果状态彼此独立, prefer 使用多个 useState 调用,而不是把它们塞进一个对象里。这样逻辑更清晰。但如果状态总是一起变化,将它们放在一个对象里也是合理的。

总结

方面 this.setState (Class) useState (Function)
范式 面向对象 函数式
状态组织 单一 state 对象 多个独立的 state 变量
更新方式 合并更新 替换更新(需手动合并)
现代性 传统方式,仍在支持 现代标准,推荐使用

更多推荐