太好了!我们继续深入 React 的核心概念 👇
这是从“静态组件”迈向“动态交互”的关键一步。掌握它,你的 React 应用才能真正“活起来”。
一、为什么需要 State?💡
在上一节中,我们学会了用 Props 接收外部数据。但 Props 是只读的,无法改变。
而现实中的 UI 是动态的:
- 用户点击按钮 → 显示/隐藏内容
- 输入表单 → 实时预览
- 计数器 → 数字递增
👉 这些都需要组件“记住一些信息”,并在未来发生变化 —— 这就是 State(状态) 。
✅ 简单说:
- Props:从父组件“传进来”的数据(外部输入)
- State:组件自己“管理的数据”,会随时间变化
二、使用 useState Hook 管理状态 🪝
现代 React 使用 Hooks 来管理状态。最基础的就是 useState。
✅ 基本语法:
import { useState } from 'react';
function Component() {
const [state, setState] = useState(初始值);
// ↑ ↑
// 当前状态 更新函数
}
🧪 示例 1:计数器
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // 初始值为 0
return (
<div>
<p>你点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>
+1
</button>
<button onClick={() => setCount(prev => prev - 1)}>
-1
</button>
</div>
);
}
⚠️ 注意:
- 不要直接修改
count(如count++),必须通过setCount()更新 - 使用函数式更新
setCount(prev => prev + 1)可避免闭包问题
🧪 示例 2:开关按钮(布尔状态)
function Toggle() {
const [isOn, setIsOn] = useState(false);
return (
<button
onClick={() => setIsOn(!isOn)}
style={{ background: isOn ? 'green' : 'gray' }}
>
{isOn ? '开' : '关'}
</button>
);
}
🧪 示例 3:文本输入框(受控组件)
function NameInput() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name} // 值由 state 控制
onChange={(e) => setName(e.target.value)} // 输入时更新 state
placeholder="请输入名字"
/>
<p>你好,{name || '陌生人'}!</p>
</div>
);
}
🔑 关键点:这个 <input> 被称为 受控组件(Controlled Component)
它的值完全由 React 状态控制,而不是浏览器自己维护。
三、事件处理详解 ⚡
React 中的事件处理和原生 DOM 类似,但有一些命名差异和注意事项。
✅ 基本写法
<button onClick={handleClick}>点我</button>
// 或内联
<button onClick={() => alert('hello')}>点我</button>
支持的常见事件:
| 事件类型 | React 写法 |
|---|---|
| 点击 | onClick |
| 输入变化 | onChange |
| 表单提交 | onSubmit |
| 鼠标移入 | onMouseEnter |
| 键盘按下 | onKeyDown |
🛠️ 事件对象 e
和原生 JS 一样,事件回调接收一个 合成事件对象(SyntheticEvent)
function handleInputChange(e) {
console.log(e.target.value); // 获取输入框的值
}
<input onChange={handleInputChange} />
💡 React 使用的是“合成事件(SyntheticEvent)”,它是跨浏览器兼容的包装器。
❗ 常见错误与避坑指南
| 错误写法 | 正确做法 | 说明 |
|---|---|---|
<button onClick={handleClick()}> | <button onClick={handleClick}> | 加括号会立即执行函数 |
| 直接修改 state 变量 | 必须用 setState(...) | 否则不会触发重新渲染 |
| 在事件中忘记阻止默认行为 | e.preventDefault() | 如表单提交刷新页面 |
✅ 正确示例:表单提交
function LoginForm() {
const [email, setEmail] = useState('');
const handleSubmit = (e) => {
e.preventDefault(); // 阻止页面刷新
alert(`登录邮箱:${email}`);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<button type="submit">登录</button>
</form>
);
}
四、State 的原则与最佳实践 ✅
1. 单一职责原则
每个 state 变量尽量只管理一种数据
// ❌ 不推荐
const [userState, setUserState] = useState({ count: 0, name: '', visible: true });
// ✅ 推荐
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const [visible, setVisible] = useState(true);
2. 使用函数式更新(Function Updates)
当新 state 依赖于旧 state 时,使用函数形式
setCount(prev => prev + 1); // 更安全,避免闭包陷阱
3. 不要在条件或循环中调用 Hook
✅ 必须在组件顶层使用 useState
// ❌ 错误
if (show) {
const [name, setName] = useState('');
}
// ✅ 正确
const [name, setName] = useState('');
这是 React Hooks 的 规则之一:只能在顶层调用 Hook。
五、实战练习 🏋️♀️
来动手练一练吧!完成以下小任务:
✅ 练习 1:简易投票应用
- 显示两个选项:“苹果” 和 “香蕉”
- 每个都有一个票数和“+1”按钮
- 点击后对应票数增加
// 提示结构
const [appleVotes, setAppleVotes] = useState(0);
const [bananaVotes, setBananaVotes] = useState(0);
✅ 练习 2:待办事项输入框
- 一个输入框 + “添加”按钮
- 输入内容后点击添加,清空输入框
- (暂不显示列表,只需实现添加逻辑)
const [text, setText] = useState('');
const handleAdd = () => {
if (text.trim()) {
alert(`添加了:${text}`);
setText(''); // 清空
}
};
✅ 练习 3:切换主题按钮
- 有一个按钮,点击切换“白天 / 夜间”模式
- 背景颜色随之变化(浅灰 / 深灰)
- 使用
className或style控制样式
const [isDark, setIsDark] = useState(false);
return (
<div style={{ background: isDark ? '#333' : '#f0f0f0', padding: 20 }}>
<button onClick={() => setIsDark(!isDark)}>
{isDark ? '白天模式' : '夜间模式'}
</button>
</div>
);
✅ 总结:State 与事件核心要点
| 概念 | 要点 |
|---|---|
useState | [value, setValue] = useState(init) |
| 更新状态 | 必须通过 setValue(...),不能直接改变量 |
| 受控组件 | 表单元素的值由 state 控制 + onChange 更新 |
| 事件处理 | 使用驼峰命名,传函数引用,非调用 |
| 合成事件 | e.target, e.preventDefault() 等 |
| 函数式更新 | setValue(prev => prev + 1) 更安全 |
| 规则 | Hook 必须在顶层调用 |
🎯 下一步预告:
你已经能让组件“动起来”了!接下来我们要学习:
➡️ 组件之间如何通信?
- 父传子(Props)
- 子传父(回调函数)
- 跨层级传递(Context)
是否继续?我可以带你进入 第三课:组件通信与数据流。