State 与 useState

Props 是"外部传进来"的数据,组件自己不能改;State 是"组件内部自己管"的数据,一变组件自动重新渲染。State 是 React 让界面"动起来"的核心机制,而 useState 是你接触的第一个 Hook。

1. 最简单的计数器

先看一个经典例子,理解 useState 的基本用法:

import { useState } from "react";

function Counter() {
  // useState 返回一个数组:[当前值, 修改函数]
  // 用解构取出,初始值是 0
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>当前:{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setCount(count - 1)}>-1</button>
      <button onClick={() => setCount(0)}>重置</button>
    </div>
  );
}

每点一次 +1 按钮,发生的事是:调用 setCount(count + 1) → React 收到新值 → 重新渲染 Counter 组件 → 页面上的数字更新。这就是 React 的核心循环:state 变化 → 触发重渲染 → UI 自动更新

2. useState 的基本规则

// useState 返回的解构名字可以随便起,但约定 set + 首字母大写
const [name, setName] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [user, setUser] = useState(null);

// ❌ 错误:setCount 不是 count++,而是"传新值"
const [count, setCount] = useState(0);
count++;            // 不行,直接改 state 不会触发重渲染
setCount(count + 1); // 正确:用 set 函数传新值

// 初始值是"昂贵计算"时,用函数形式(只跑一次)
const [data, setData] = useState(() => loadFromStorage());
// 而不是 useState(loadFromStorage()),后者每次渲染都跑(慢)

几个要点:useState(初始值) 返回一个二元数组,用解构 const [值, set函数] = useState(初始值) 取出。set 函数的作用是"告诉 React 这个 state 变了,请用新值重渲染"。不调用 set,组件永远不会更新

3. 不可变更新(最重要的概念)

这是 React 新手最容易踩的坑,务必吃透:state 是不可变的,你必须给 set 函数传一个全新的对象/数组,而不是直接改原来的。

// ❌ 错误:直接改对象字段,React 检测不到变化
const [user, setUser] = useState({ name: "小明", age: 20 });
user.age = 21;        // 直接改,React 不会重渲染!
setUser(user);        // 引用没变,React 认为没变化

// ✅ 正确:用展开运算符造一个新对象
setUser({ ...user, age: 21 });   // 新对象,引用变了,触发更新

// ❌ 错误:数组直接 push
const [list, setList] = useState([1, 2, 3]);
list.push(4);         // 直接改,不触发更新

// ✅ 正确:造一个新数组
setList([...list, 4]);           // 末尾加
setList([0, ...list]);           // 开头加
setList(list.filter(x => x !== 2));  // 删除元素
setList(list.map(x => x * 2));  // 修改元素

为什么?因为 React 判断"state 是否变化"用的是引用比较(Object.is)。你直接改 user.age,user 这个变量指向的对象还是同一个(引用没变),React 就认为"没变化",不重渲染。造一个新对象 { ...user, age: 21 },引用变了,React 才会更新。

记住这个口诀:改 state 等于"换一个新值",不是"在原值上改"。数组用 ...filtermap 这些"返回新数组"的方法;对象用 ... 展开。

4. 函数式更新:依赖上一次的值时

当新 state 依赖上一次的 state 时(比如 count + 1),直接用 count 可能踩"闭包陷阱"——因为 count 是本次渲染时的快照,连续多次调用 set 会用到旧值:

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

// ❌ 依赖上一次的值时,直接用 count 有"闭包陷阱"
// 连续调用三次,setCount(count + 1) 实际只加了 1
const bad = () => {
  setCount(count + 1);
  setCount(count + 1);
  setCount(count + 1);
};

// ✅ 正确:用"函数式更新",React 把最新值传给你
const good = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);
};
// 调用 good() 后,count 会变成 3

规则:新 state 不依赖旧值,直接传值;依赖旧值,传函数 prev => ...。养成这个习惯,能避免一大类难调的 bug。

5. 多个 state vs 一个对象 state

一个组件可以有任意多个 state:

// 一个组件可以有多个 state
function SignupForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [agreed, setAgreed] = useState(false);
  const [errors, setErrors] = useState([]);

  // 当 state 互相关联时,可以合并成一个对象
  // 但更新时要记得展开(不可变)
  // const [form, setForm] = useState({ name: "", email: "" });
  // setForm(prev => ({ ...prev, name: "新名字" }));

  return (
    <form>
      <input value={name} onChange={e => setName(e.target.value)} />
      <input value={email} onChange={e => setEmail(e.target.value)} />
    </form>
  );
}

经验法则:互相独立的字段,用多个 state(简单清晰);强相关的字段(如表单的多个字段),合并成一个对象 state(更新时记得展开)。不要走极端——全用多个 state 会啰嗦,全塞一个对象会臃肿。

6. state 是"快照"

React 的 state 有个反直觉的特性:一次渲染里,count 是一个固定不变的快照。即使你调用了 setCount(count + 1),本次渲染里 count 还是旧值——新值要等下次渲染才生效。

理解这一点,你就理解了 React 的"函数式"内核——它和传统的"变量赋值"思维完全不同。

7. state 要放对地方

不是所有数据都要放 state。判断原则:

小结

State 让组件"活"起来。useState 返回值和 set 函数,改 state 必须传新值(不可变),依赖旧值时用函数式更新。掌握不可变更新,React 你就懂了一半。下一篇看用户操作怎么触发 state 变化——事件处理

← 上一篇 Props 属性

下一篇 React 事件处理

✈️💬