第 4 章:State
学习目标
- 理解 State 是组件内部的"记忆"
- 掌握
useState的 4 种用法 - 理解 State 不可变的概念
- 避开 State 的 5 个常见坑
一、State 是什么?
State = 组件内部会变化的数据。改 state → 组件自动重渲染。
tsx
function Counter() {
const [count, setCount] = useState(0); // 初始值 0
return (
<div>
<p>点击次数:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}要点:
useState返回[值, 更新函数]- 更新函数触发重渲染
- 多次调用会合并(React 18 batching)
二、State vs Props
| 维度 | State | Props |
|---|---|---|
| 来源 | 组件内部声明 | 父组件传入 |
| 可修改 | ✅(通过 setter) | ❌(只读) |
| 影响范围 | 当前组件 + 子组件 | 仅当前组件 + 子组件(只读) |
| 触发重渲染 | 改值即触发 | 父组件改值传下来才触发 |
三、useState 的 4 种用法
1. 数字
tsx
const [count, setCount] = useState(0);
setCount(count + 1); // 设新值
setCount(prev => prev + 1); // ✅ 函数式更新,推荐2. 字符串
tsx
const [name, setName] = useState('');
setName('张三');3. 布尔
tsx
const [loading, setLoading] = useState(false);
setLoading(true);4. 对象/数组
tsx
const [user, setUser] = useState({ name: '', age: 18 });
const [list, setList] = useState<string[]>([]);
// �️ 不能直接改原对象,要创建新对象
setUser({ ...user, name: '张三' }); // ✅ 创建新对象
user.name = '张三'; // ❌ 直接改不会重渲染⚠️ 坑 1:State 是不可变的(immutable),改值必须新建对象/数组,React 靠引用变化判断要不要重渲染。
四、函数式更新(异步陷阱)
多次连续 setCount(count + 1) 在异步环境下可能只生效一次:
tsx
function handleClick() {
setCount(count + 1); // count = 0,加 1 = 1
setCount(count + 1); // count 还是 0,加 1 = 1(不是 2!)
setCount(count + 1); // 还是 1
}正确写法:函数式更新,参数是上一个 state:
tsx
function handleClick() {
setCount(prev => prev + 1); // 0 + 1 = 1
setCount(prev => prev + 1); // 1 + 1 = 2
setCount(prev => prev + 1); // 2 + 1 = 3
}闭包陷阱的本质
count 是 render 时拍下来的"快照",写死在 handler 里就是固定值——三次 setCount(count + 1) 用的都是同一个旧值 0。
| 写法 | React 收到啥 |
|---|---|
setCount(count + 1) | 一个固定数字(0 + 1 = 1,写死的) |
setCount(prev => prev + 1) | 一个函数(让 React 自己查最新值再算) |
prev 不是闭包变量,是 React 内部维护的"上一步真实结果"——每次都基于上一步算。
什么时候用哪种写法
| 写法 | 何时用 |
|---|---|
setX(prev => ...) 函数式 | 新值依赖旧值 |
setX(新值) 普通 | 新值跟旧值无关 |
用函数式的 5 个典型场景:
tsx
// ① 累加/累减
setCount(prev => prev + 1)
// ② 连续 toggle boolean
setOpen(prev => !prev)
// ③ 基于旧数组筛选/追加/删除
setList(prev => prev.filter(x => x.id !== id))
setList(prev => [...prev, newItem])
// ④ 对象里改单个字段
setForm(prev => ({ ...prev, name: '张三' }))
// ⑤ 异步回调里改 state(必须!)
setTimeout(() => {
setCount(prev => prev + 1) // ← 异步里闭包陷阱更明显
}, 1000)用普通写法的场景:
tsx
// ① 改成一个固定值(重置)
setCount(0)
setName('张三')
// ② 从外部拿来的值(不依赖旧 state)
setName(inputValue) // input 框直接绑定
setData(res.data) // 接口返回的
// ③ 多个独立状态
setName('zs')
setAge(20)�️ 坑 2:依赖上一个 state 时必须用函数式更新,否则连续多次调用只生效最后一次。
五、对象 State 的正确改法
tsx
const [form, setForm] = useState({ name: '', email: '' });
// ✅ 改单个字段
setForm({ ...form, name: '张三' });
// ✅ 函数式
setForm(prev => ({ ...prev, email: 'a@b.com' }));
// ❌ 直接改原对象,不会重渲染
form.name = '张三';简化套路:
tsx
function update<K extends keyof Form>(key: K, value: Form[K]) {
setForm(prev => ({ ...prev, [key]: value }));
}
// 使用
update('name', '张三');
update('email', 'a@b.com');六、数组 State 的正确改法
tsx
const [todos, setTodos] = useState<Todo[]>([]);
// 追加
setTodos([...todos, { id: Date.now(), text: '新任务' }]);
// 删除
setTodos(todos.filter(t => t.id !== id));
// 修改某个
setTodos(todos.map(t => t.id === id ? { ...t, done: !t.done } : t));
// 排序
setTodos([...todos].sort((a, b) => a.id - b.id));⚠️ 坑 3:
push/pop/splice等会修改原数组的方法不能用,要用concat/filter/map/slice这类返回新数组的方法。
七、State 提升(共享状态)
兄弟组件共享 state,提升到最近共同父组件:
tsx
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<DisplayA count={count} />
<DisplayB count={count} />
<button onClick={() => setCount(c => c + 1)}>+</button>
</>
);
}
function DisplayA({ count }: { count: number }) {
return <div>A 显示:{count}</div>;
}
function DisplayB({ count }: { count: number }) {
return <div>B 显示:{count}</div>;
}八、本章小结
| 要点 | 关键 |
|---|---|
useState | 返回 [值, setter] |
| 不可变更新 | 对象/数组要新建,不能直接改 |
| 函数式更新 | setX(prev => ...) 避免异步陷阱 |
| 数组操作 | 用 map/filter/slice,不用 push/splice |
| 对象操作 | {...obj, key: value} |
| State 提升 | 兄弟共享 state 提到共同父组件 |
动手练习
- 计数器:做一个
±N按钮,传入 step 控制步长(用函数式更新) - Todo 列表:增、删、改、勾选完成,所有操作通过 setter
- 表单对象:维护
{username, password, remember},封装update(key, value)辅助函数 - 找坑:下面代码哪里错?tsx
const [list, setList] = useState([1, 2, 3]); list.push(4); setList(list);
下一章:第 5 章:事件处理 →