React: State 基础:useState
最后更新:2026-10-26
State 是组件的 记忆。如果说 Props 是外人给你的参数(只读),那 State 就是你自己的小本本(可以改)。useState 就是 React 给组件装上的"记忆芯片"。
1. 你将学到
- 什么是 State,为什么需要 State
- useState 的基本用法
- 数组和对象状态的正确更新方式(不可变性)
- 多个 State 变量的管理
- 状态提升(Lifting State Up)
2. 一个计数器引发的思考
(1) 痛点:普通变量不会触发 UI 更新
Bob 想做一个简单的计数器:点击按钮,数字 +1。
▶ 试一试Bob 发现问题了:普通变量不会通知 React 更新 UI。每次组件重新渲染时,count 都会被重置为 0。
(2) useState 的解法
▶ 试一试收益:useState 返回一个数组 [当前值, 更新函数]。调用 setCount 后,React 会自动重新渲染组件,显示最新的值。
3. useState 的核心概念
(1) 解构赋值
JSX
import { useState } from 'react'
// useState(initialValue) 返回 [value, setValue]
const [count, setCount] = useState(0)
// ^ ^ ^
// | | └─ 初始值(只在首次渲染生效)
// | └─ 更新函数(调用后触发重新渲染)
// └─ 当前状态值(每次渲染获取最新值)
| 部分 | 说明 |
|---|---|
count |
当前状态值,每次渲染都拿到最新值 |
setCount |
更新函数,调用它 → React 重新渲染组件 |
useState(0) |
初始值(首次渲染时使用,后续渲染忽略) |
(2) 更新机制
▶ 试一试关键理解:
setCount(count + 1)是"请求 React 在下次渲染时更新 count",而不是"立即修改 count"。这叫做异步更新。
(3) 函数式更新
如果新状态依赖旧状态,应该用函数式更新:
▶ 试一试| 方式 | 写法 | 特点 |
|---|---|---|
| 直接更新 | setCount(count + 1) |
简单,但连续调用会丢失 |
| 函数式更新 | setCount(prev => prev + 1) |
准确,连续调用也正确 |
4. 数组和对象的更新(不可变性)
React 要求状态不可直接修改。永远不要做 state.push() 或 state.name = 'xxx',要创建新数组/新对象替换旧值。
(1) 更新对象
▶ 试一试(2) 更新数组
▶ 试一试输出:
TEXT
状态:arr
▶ 示例:数组操作速查表
JSX
// ============================================
// 示例:React 中数组操作的 6 种常见场景
// ============================================
const [arr, setArr] = useState([1, 2, 3])
// 1. 追加到末尾
setArr([...arr, 4]) // [1, 2, 3, 4]
// 2. 追加到开头
setArr([0, ...arr]) // [0, 1, 2, 3]
// 3. 插入到中间
const insertAt = 1
setArr([...arr.slice(0, insertAt), 99, ...arr.slice(insertAt)])
// [1, 99, 2, 3]
// 4. 删除元素(filter)
setArr(arr.filter(n => n !== 2)) // [1, 3]
// 5. 更新元素(map)
setArr(arr.map(n => n === 2 ? 22 : n)) // [1, 22, 3]
// 6. 排序(先复制再排序)
setArr([...arr].sort((a, b) => b - a)) // [3, 2, 1]
输出:
TEXT
数组状态操作:push → 添加项、filter → 删除项、map → 更新项。每个操作都返回新的数组引用,以便 React 重新渲染
5. 多个 State 变量
一个组件可以有多个 State 变量,建议按逻辑拆分,而不是塞进一个大对象:
JSX
function RegistrationForm() {
// ✅ 推荐:按逻辑拆分
const [name, setName] = useState('')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [errors, setErrors] = useState({})
// ❌ 不推荐:所有状态塞进一个对象
const [form, setForm] = useState({
name: '', email: '', password: '',
isSubmitting: false, errors: {}
})
// 更新时要展开整个对象,容易漏掉字段
}
| 策略 | 优点 | 缺点 |
|---|---|---|
| 分开多个 useState | 更新精确、类型安全、易于阅读 | 状态多时变量多 |
| 合在一个 useState | 一次性更新多个字段方便 | 更新语法繁琐,容易忘展开 |
建议:独立的状态(输入框、加载标志、错误信息)分开用多个 useState;逻辑上相关的字段(如用户信息的所有字段)可以合并为一个对象。
6. 状态提升(Lifting State Up)
当多个组件需要共享同一个状态时,把状态提升到它们最近的共同父组件中。
graph TB
subgraph "状态提升前(数据不互通)"
A[父组件 App] --- B[子组件 A<br/>有自己的 count]
A --- C[子组件 B<br/>有自己的 count]
end
subgraph "状态提升后(数据共享)"
D[父组件 App<br/>**count 在这里**] --- E[子组件 A<br/>读取 count, 调用 setCount]
D --- F[子组件 B<br/>读取 count, 调用 setCount]
end
输出:
TEXT
状态:arr(setter: setArr)
▶ 示例:状态提升实战
▶ 试一试输出:
TEXT
在摄氏输入 100 → 华氏度自动显示 212.0。显示 "100°C = 212.0°F"。两个输入框保持同步。
7. 完整示例:购物车计数器
▶ 展开运行示例(96 行)
输出:
TEXT
副标题:"Temperature Converter"。显示:"Celsius(°C):"。状态:temperature(setter: setTemperature)
交互流程:
- 点 +/− 调整数量,小计和合计实时更新
- 点 ✕ 删除商品,空购物车时显示友好提示
- 输入折扣码
REACT2026可减 20 元
▶ 示例 3:对象状态更新——用户信息编辑
JSX
function UserEditor() {
const [user, setUser] = useState({ name: 'Alice', email: 'alice@test.com', age: 28 })
function updateField(field, value) {
setUser(prev => ({ ...prev, [field]: value }))
}
return (
<div style={{ maxWidth: 400, margin: '0 auto' }}>
<h3>Edit Profile</h3>
<input value={user.name} onChange={e => updateField('name', e.target.value)}
placeholder="Name" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<input value={user.email} onChange={e => updateField('email', e.target.value)}
placeholder="Email" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<input type="number" value={user.age} onChange={e => updateField('age', Number(e.target.value))}
placeholder="Age" style={{ width: '100%', padding: 8, marginBottom: 8, borderRadius: 4 }} />
<pre style={{ background: '#f5f5f5', padding: 12, borderRadius: 4, fontSize: 13 }}>
{JSON.stringify(user, null, 2)}
</pre>
</div>
)
}
输出:
TEXT
状态:address。输入框:Street、City、ZIP
输出:
TEXT
显示:"Edit Profile"。状态:user(setter: setUser)。输入框:Name、Email、Age
▶ 示例 4:嵌套对象状态——地址管理
▶ 试一试输出:
TEXT
收货表单:Street、City、ZIP、国家下拉框(US/CN/JP)、"Primary address" 复选框。所有字段都响应式地更新状态。
输出:
TEXT
显示:"Shipping Address"。状态:address(setter: setAddress)。输入框:Street、City、ZIP。下拉框:United States、China、Japan
▶ 示例 5:函数式更新解决批量更新问题
▶ 试一试输出:
TEXT
Score: 0、Multiplier: x1。+10/+50/+100 按 分数×倍率 加分。"2x Multiplier" 使倍率翻倍(最高 x8)。Reset → Score: 0、x1
❓ 常见问题
Q useState 和普通变量的性能有什么区别?
A useState 有额外开销(追踪变化、调度渲染),所以只在需要"变化时触发 UI 更新"的场景使用。如果变量变化不需要更新 UI(如定时器 ID、滚动位置记录),用
useRef(第 10 课)更合适。Q 一个组件里可以有多少个 useState?有性能问题吗?
A 没有数量限制。React 官方建议按逻辑拆分为多个 useState 而不是一个大对象。每个 useState 调用就是一个"状态单元",React 能高效处理大量 useState。常见组件通常有 3-8 个 useState。
Q setState 更新是同步还是异步的?
A 在 React 18 之前,setState 在事件处理函数中是"批量异步"的,在 setTimeout/Promise 中是同步的。React 18 引入了自动批量处理(Automatic Batching),无论在事件处理、setTimeout、Promise 还是 fetch 回调中,setState 都是批量异步的。如果确实需要同步获取更新后的 DOM,可以用
flushSync(() => setState(...))。📖 小节
- State 是组件的"记忆",变化时自动触发 UI 更新
useState(initialValue)返回[value, setValue]数组- 不可变性:永远不要直接修改 state,要创建新对象/新数组替换
- 函数式更新
setCount(prev => prev + 1)在依赖旧值时更准确 - 状态提升:多个子组件需要共享数据时,状态提到共同父组件
📝 作业
- 基础题(难度⭐):创建一个
LikeButton组件,点击切换 ❤️ / 🤍 状态,并显示点赞数。 - 进阶题(难度⭐⭐):创建一个
ExpenseTracker组件,用 useState 管理支出列表(描述、金额、日期),支持添加和删除。 - 挑战题(难度⭐⭐⭐):创建一个
ColorPicker组件,用 3 个 useState 管理 R/G/B 三个滑块值(0-255),实时显示颜色预览和十六进制色码。