Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

hook:useState

hook 的作用

使函数式组件也能有自身的状态和副作用,而无需编写类组件

  • 注意

    • 不能在类内使用,只能在函数内顶层调用 hook
    • 不能在循环、条件或嵌套函数中调用 hook
    • 但可以 自定义 hook(必须 use 开头) 来封装 底层 hook

useState

  • 用来定义状态变量,可以替代以前类里构造函数的 state 和 setState
  • 核心特点: 状态改变会触发组件重新渲染

场景代码示例

  • 【场景 1】基础类型状态(最常用):

    • 定义数字、字符串、布尔值等基础类型的状态

      // 场景:计数器 - 使用数字状态
      import { useState } from "react";
      
      const Counter = () => {
        const [count, setCount] = useState(0); // 初始值为 0
      
        return (
          <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>Click me</button>
          </div>
        );
      };
      
    • 如果要用以前的类去写,会很麻烦

      class Counter extends React.Component {
        constructor(props) {
          super(props);
          this.state = {
            count: 0,
          };
        }
      
        render() {
          return (
            <div>
              <p>You clicked {this.state.count} times</p>
              <button
                onClick={() =>
                  this.setState({
                    count: this.state.count + 1,
                  })
                }
              >
                Click me
              </button>
            </div>
          );
        }
      }
      
  • 【场景 2】对象状态(常用):

    • 定义对象类型的状态,需要注意整体替换

      // 场景:表单 - 使用对象状态
      const Form = () => {
        const [user, setUser] = useState({ name: "", age: 0 });
      
        // ❌ 错误:直接修改对象(React 不会检测到变化)
        const wrongUpdate = () => {
          user.name = "Alice"; // 不会触发重新渲染!
        };
      
        // ✅ 正确:创建新对象
        const correctUpdate = () => {
          setUser({ ...user, name: "Alice" }); // 展开运算符创建新对象
        };
      
        return (
          <div>
            <p>
              Name: {user.name}, Age: {user.age}
            </p>
            <button onClick={correctUpdate}>Update Name</button>
          </div>
        );
      };
      
    • 注意: setState 是替换而非合并,必须用“展开运算符”保留其他属性

  • 【场景 3】数组状态(常用):

    • 定义数组类型的状态,需要使用不可变方法

      // 场景:待办列表 - 使用数组状态
      const TodoList = () => {
        const [todos, setTodos] = useState(["Learn React", "Build App"]);
      
        // ❌ 错误:直接修改数组
        const wrongAdd = () => {
          todos.push("New Todo"); // 不会触发重新渲染!
          setTodos(todos);
        };
      
        // ✅ 正确:创建新数组
        const correctAdd = () => {
          setTodos([...todos, "New Todo"]); // 展开运算符创建新数组
        };
      
        const removeItem = (index) => {
          setTodos(todos.filter((_, i) => i !== index)); // filter 返回新数组
        };
      
        return (
          <div>
            {todos.map((todo, i) => (
              <div key={i}>
                {todo} <button onClick={() => removeItem(i)}>删除</button>
              </div>
            ))}
            <button onClick={correctAdd}>Add Todo</button>
          </div>
        );
      };
      
    • 常用不可变方法: [...arr]filtermapsliceconcat

  • 【场景 4】函数式更新(重要):

    • 基于前一个状态更新,避免闭包陷阱

      // 场景:多次更新 - 使用函数式更新
      const Counter = () => {
        const [count, setCount] = useState(0);
      
        // ❌ 错误:连续调用可能不生效
        const wrongIncrement = () => {
          setCount(count + 1); // count 是闭包中的旧值
          setCount(count + 1); // 还是基于旧值
          setCount(count + 1); // 还是基于旧值
          // 结果:只加 1,而不是加 3
        };
      
        // ✅ 正确:使用函数式更新
        const correctIncrement = () => {
          setCount((prev) => prev + 1); // 基于最新值
          setCount((prev) => prev + 1); // 基于最新值
          setCount((prev) => prev + 1); // 基于最新值
          // 结果:加 3
        };
      
        return (
          <div>
            <p>Count: {count}</p>
            <button onClick={wrongIncrement}>错误 +3</button>
            <button onClick={correctIncrement}>正确 +3</button>
          </div>
        );
      };
      
    • 规则: 当新状态依赖旧状态时,使用函数式更新 setState(prev => newValue)

  • 【场景 5】惰性初始化(优化):

    • 初始值需要复杂计算时,使用函数避免每次渲染都计算

      // 场景:昂贵的初始化 - 使用惰性初始化
      const ExpensiveComponent = () => {
        // ❌ 每次渲染都会执行(即使用不到)
        const [data, setData] = useState(expensiveComputation());
      
        // ✅ 只在初始化时执行一次
        const [data2, setData2] = useState(() => expensiveComputation());
      
        return <div>{data2}</div>;
      };
      
      function expensiveComputation() {
        console.log("计算中...");
        let result = 0;
        for (let i = 0; i < 1000000000; i++) {
          result += i;
        }
        return result;
      }
      
    • 适用场景: 从 localStorage 读取、复杂计算、解析大型数据

  • 【场景 6】多个状态管理(常用):

    • 定义多个独立的状态变量

      // 场景:用户信息 - 多个独立状态
      const UserProfile = () => {
        const [name, setName] = useState("Alice");
        const [age, setAge] = useState(25);
        const [email, setEmail] = useState("alice@example.com");
      
        // 或者合并为一个对象(看具体需求)
        const [user, setUser] = useState({
          name: "Alice",
          age: 25,
          email: "alice@example.com",
        });
      
        return (
          <div>
            <p>Name: {name}</p>
            <p>Age: {age}</p>
            <p>Email: {email}</p>
          </div>
        );
      };
      
    • 选择原则:

      • 相关联的数据 → 合并为一个对象
      • 独立的数据 → 分开定义多个状态

状态更新方式对比

更新方式代码示例适用场景
直接赋值setCount(5)新值与旧值无关
基于当前值setCount(count + 1)单次更新,且值不在闭包中
函数式更新setCount(prev => prev + 1)基于旧值更新、连续更新
对象展开setUser({...user, age: 26})更新对象的部分属性
数组展开setList([...list, item])添加数组元素
数组过滤setList(list.filter(...))删除数组元素
惰性初始化useState(() => compute())初始值需要复杂计算

useState vs 类组件 state

特性useState类组件 state
定义方式const [x, setX] = useState(0)this.state = { x: 0 }
更新方式setX(1)this.setState({ x: 1 })
更新是否合并❌ 替换✅ 合并
可以定义多个✅ 分开定义多个状态❌ 只有一个 state 对象
函数式更新setX(prev => prev + 1)this.setState(prev => ({...}))
代码简洁度✅ 更简洁❌ 需要 this、constructor

注意事项

  • ⚠️ 不要直接修改状态

    // ❌ 错误
    const [user, setUser] = useState({ name: "Alice" });
    user.name = "Bob"; // 不会触发重新渲染
    
    // ✅ 正确
    setUser({ ...user, name: "Bob" }); // 创建新对象
    
  • ⚠️ 状态更新是异步的

    setCount(count + 1);
    console.log(count); // 还是旧值,不会立即更新
    
  • ⚠️ 对象/数组需要创建新引用

    // ❌ React 不会检测到变化
    const [list, setList] = useState([1, 2, 3]);
    list.push(4);
    setList(list); // 引用没变,不会重新渲染
    
    // ✅ 创建新数组
    setList([...list, 4]); // 新引用,触发重新渲染
    
  • 连续更新使用函数式更新

    // 多次更新时,使用函数形式保证基于最新值
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
    
  • 初始化开销大时使用惰性初始化

    // 使用函数,只在初始化时执行一次
    useState(() => expensiveComputation());