React: State 基础:useState

最后更新:2026-10-26

State 是组件的 记忆。如果说 Props 是外人给你的参数(只读),那 State 就是你自己的小本本(可以改)。useState 就是 React 给组件装上的"记忆芯片"。


1. 你将学到



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)

当多个组件需要共享同一个状态时,把状态提升到它们最近的共同父组件中。

100%
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(...))。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 LikeButton 组件,点击切换 ❤️ / 🤍 状态,并显示点赞数。
  2. 进阶题(难度⭐⭐):创建一个 ExpenseTracker 组件,用 useState 管理支出列表(描述、金额、日期),支持添加和删除。
  3. 挑战题(难度⭐⭐⭐):创建一个 ColorPicker 组件,用 3 个 useState 管理 R/G/B 三个滑块值(0-255),实时显示颜色预览和十六进制色码。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏