01 · NOTES / WebGIS 开发
第 03 节 · state 与 useState
2026 年 9 月 1 日
第 03 节 · state 与 useState
📌 版本信息:React 19.x(2026-08-29 核对) 📚 来源:React 中文文档 · State:组件的记忆 | 渲染与提交
一、这一节的目标
- 理解 state:组件的"私有记忆",变化触发重渲染
- 掌握 useState 三件套:声明、读取、更新
- 精通不可变更新(为什么不能 push、要造新数组)
- 理解 setState 的"排队"语义(异步批处理)
- 完成"计数器 + 受控输入"练习
二、state:组件的记忆
一句话:普通变量在组件每次渲染时都会重新创建;state 是 React 帮你跨渲染保存的组件私有数据——它一变,组件自动重渲染。
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
// ↑ 当前值 ↑ 更新函数 ↑ 初始值
// 读法:"声明一个叫 count 的 state,初始 0,用 setCount 改它"
return (
<button onClick={() => setCount(count + 1)}>
点了 {count} 次
</button>
);
}
// 点击 → setCount(1) → React 记住新值 → 重新执行组件函数 → 界面显示 1
// 再点 → setCount(2) → 再渲染 → 显示 2
// ⚠️ 直接 count++ 毫无效果:普通变量改了就丢,React 根本不知道要重渲染
渲染循环:setState(新值) → React 调度 → 重新执行组件函数(拿到新 state)→ Diff 差异 → 只更新变化的部分。组件函数会被反复执行——所以它必须纯粹(第 02 节)。
三、不可变更新(本节最重点)
规则:永远用 setState 创建"新值",绝不直接修改旧值。
const [todos, setTodos] = useState([...]);
// ❌ 错误:原地修改(React 比较的是引用,旧引用没变 → 认为没变化 → 不重渲染)
todos.push(newTodo);
setTodos(todos);
// ✅ 正确:造新数组/新对象
setTodos([...todos, newTodo]); // 数组加一项
setTodos(todos.filter((t) => t.id !== id)); // 删一项
setTodos(todos.map((t) => (t.id === id ? { ...t, done: !t.done } : t))); // 改一项
setCity({ ...city, name: '武汉新版' }); // 对象改字段
这正是第 12 节"不可变更新"的 React 应用场景——当时埋的种子发芽了。原因两层:①React 靠引用比较判断"变没变";②纯函数要求不碰外部状态。
函数式更新:基于"上一份"而不是"外面那份"
// ❌ 连续两次 +1 可能只加一次(两次 setCount 拿到的都是同一个旧 count)
setCount(count + 1);
setCount(count + 1);
// ✅ 传函数:React 会把上一个更新结果传进来(prev)
setCount((prev) => prev + 1);
setCount((prev) => prev + 1); // 稳定 +2
// 经验法则:新值依赖旧值时,一律用函数式更新
四、批处理:setState 不是立刻生效
function handleClick() {
setCount(count + 1);
console.log(count); // ⚠️ 打印的还是旧值!
}
setState 是"提交申请"不是"立即生效"——React 把同一事件里的多次 setState 批量收集、合并成一次渲染(性能优化)。所以:
- 事件处理函数里 setState 之后读 state,读到的还是旧值
- 要"基于上一次的值",用函数式更新(上一小节)
- 需要在"渲染完成后做事",用 useEffect(第 04 节)
五、动手跟练:03 · 计数器与表单
配套文件夹:03-react-nextjs/examples/03-计数器与表单/(npm i && npm run dev)
步骤:
- 读代码:三个 state(count、step、输入文本)各管一摊
- 完成 6 个 TODO:步长选择(1/5/10)、重置按钮、函数式更新实验(连点两次对比两种写法)、受控输入框、修改 props 的警告实验、把第 14 节待办列表用 React 重写预告
- 重点实验:
setCount(count+1)连点 vssetCount(prev=>prev+1)连点——亲眼看批处理的影响
通关标准:
- 能说出"为什么不能 push 直接改数组"
- 函数式更新能默写
- 能解释"setState 后 log 还是旧值"
六、自测题
- 组件内普通变量
let n = 0; n++为什么界面不变? - 不可变更新两层原因是什么?
setCount(prev => prev + 1)与setCount(count + 1)的区别?- state 和 props 的本质区别一句话?
- 同一事件里 setState 五次,渲染几次?
参考答案
- 普通变量活在"这一次渲染"里,组件函数重执行时重新初始化;React 也不知道要重渲染。
- React 用引用比较判断数据是否变化;组件纯粹性要求不改外部/入参数据。
- 前者基于 React 维护的"最新排队值",多次调用稳定累加;后者基于本次渲染捕获的旧值,批处理下会丢更新。
- state 是组件私有的、可变的(通过 setter);props 是父给的、只读的。
- 只渲染 1 次(批处理合并),但 state 的最终值由五个更新依次作用决定。
七、下一步
数据会变了 → 第 04 节:useEffect,处理"变了之后还要做什么"(发请求、操作定时器)。
TAGSweb