详细讲解 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 组件 setStateFunction 组件 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 变量
更新方式合并更新替换更新(需手动合并)
现代性传统方式,仍在支持现代标准,推荐使用

更多推荐