第11章 State与事件处理
让组件能够响应用户操作并更新界面
作者:zyyc-0336 | 日期:2026-06-12
📚 学习路径建议
- 先学 11.1(事件绑定),掌握 React 中如何响应用户操作
- 再学 11.2-11.3(useState 和状态不可变性),理解 React 的"数据驱动 UI"
- 最后学 11.4(受控组件),学会在 React 中处理表单
⚠️ 常见错误提示
- ❌ 不要直接修改 state:
count++❌,必须用setCount(count + 1)✅ useState只能在组件的最顶层调用,不能在 if/for/函数里- 事件名用驼峰命名:
onClick不是onclick,onChange不是onchange - 事件处理函数不要加括号:
onClick={'{handleClick}'}✅,onClick={'{handleClick()}'}❌
11.1 事件绑定
事件绑定就是告诉 React "当用户做了什么操作,要执行什么代码"。React 中事件名用驼峰命名,事件值是一个函数。
💻 完整示例代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>事件绑定示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function EventDemo() {
// 方式1:内联函数(简单逻辑用)
// 方式2:定义函数后引用(复杂逻辑用)
function handleClick() {
alert('你点击了按钮!');
}
function handleMouseEnter() {
console.log('鼠标移入了');
}
return (
<div style={{ padding: '20px' }}>
<h1>事件绑定</h1>
{/* 方式1:内联箭头函数 */}
<button onClick={() => alert('内联函数')}>
内联函数
</button>
{/* 方式2:引用函数(推荐,不加括号) */}
<button onClick={handleClick}>
引用函数
</button>
{/* 鼠标事件 */}
<div
onMouseEnter={handleMouseEnter}
onMouseLeave={() => console.log('鼠标移出了')}
style={{
width: '200px',
height: '100px',
backgroundColor: '#3498db',
marginTop: '10px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
borderRadius: '8px'
}}
>
鼠标移过来
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<EventDemo />);
</script>
</body>
</html>
常见事件
| 事件 | React 写法 | HTML 写法 | 说明 |
|---|---|---|---|
| 点击 | onClick | onclick | 按钮点击 |
| 双击 | onDoubleClick | ondblclick | 双击 |
| 鼠标移入 | onMouseEnter | onmouseenter | 鼠标进入 |
| 鼠标移出 | onMouseLeave | onmouseleave | 鼠标离开 |
| 输入变化 | onChange | onchange | 输入框内容变化 |
| 表单提交 | onSubmit | onsubmit | 表单提交 |
| 键盘按下 | onKeyDown | onkeydown | 按键 |
⚠️ 新手易错:
onClick={'{handleClick()}'}加了括号,函数会立即执行而不是点击时执行。正确写法:onClick={'{handleClick}'}(引用函数)或onClick={'{() => handleClick()}'}(箭头函数包裹)。
11.2 useState 状态管理
State(状态)是组件内部的"记忆"。没有 State 的组件就像金鱼——只有7秒记忆,每次刷新就忘了之前发生了什么。useState 是 React 提供的 Hook,用来给组件添加状态。
基本语法
let [状态变量, 更新函数] = useState(初始值);
💻 完整示例代码(点赞按钮 + 多状态)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>useState 示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// ========== 点赞按钮 ==========
function LikeButton() {
// count 是状态值,setCount 是更新函数
let [count, setCount] = React.useState(0);
function handleLike() {
setCount(count + 1); // 更新状态,组件重新渲染
}
return (
<button onClick={handleLike} style={{
padding: '10px 20px',
fontSize: '16px',
cursor: 'pointer',
border: '1px solid #e74c3c',
borderRadius: '20px',
backgroundColor: 'white',
color: '#e74c3c'
}}>
❤️ {count}
</button>
);
}
// ========== 多个状态 ==========
function ArticleInfo() {
let [likes, setLikes] = React.useState(0);
let [isBookmarked, setIsBookmarked] = React.useState(false);
return (
<div style={{
border: '1px solid #ddd',
padding: '20px',
borderRadius: '8px',
marginTop: '20px'
}}>
<h2>JavaScript 入门指南</h2>
<p>作者:小明 · 2026-06-12</p>
<div style={{ display: 'flex', gap: '10px', alignItems: 'center' }}>
<button onClick={() => setLikes(likes + 1)}>
❤️ 点赞 ({likes})
</button>
<button onClick={() => setIsBookmarked(!isBookmarked)}>
{isBookmarked ? '⭐ 已收藏' : '☆ 收藏'}
</button>
</div>
</div>
);
}
function App() {
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<h1>useState 状态管理</h1>
<LikeButton />
<ArticleInfo />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
代码逐行讲解
| 代码 | 说明 |
|---|---|
let [count, setCount] = React.useState(0) | 声明状态 count,初始值为 0 |
count | 当前状态值 |
setCount(count + 1) | 更新状态,React 会重新渲染组件 |
let [isBookmarked, setIsBookmarked] = React.useState(false) | 布尔类型状态 |
setIsBookmarked(!isBookmarked) | 切换 true/false |
setState 的工作流程
→ 调用 setCount(count + 1)
→ React 更新 count 的值
→ React 重新执行组件函数
→ 返回新的 JSX
→ 页面更新
💡 核心原理:每次调用
setState,React 会用新值重新执行整个组件函数,得到新的 JSX,再把变化的部分更新到页面上。这就是"数据驱动 UI"。
11.3 状态不可变性
状态不可变性:永远创建新值来更新状态,而不是修改旧值。这是 React 的铁律——不能直接修改 state,必须用 setState 创建新值。
💻 完整示例代码(评论增删 + 对象更新)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>状态不可变性示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
function App() {
// 数组状态
let [comments, setComments] = React.useState([
{ id: 1, user: '小明', text: '写得好!' },
{ id: 2, user: '小红', text: '收藏了' }
]);
// ========== 添加评论(数组) ==========
function addComment() {
let newComment = {
id: Date.now(),
user: '新用户',
text: '新评论 ' + new Date().toLocaleTimeString()
};
// ✅ 正确:创建新数组
setComments([...comments, newComment]);
// ❌ 错误:comments.push(newComment); setComments(comments);
}
// ========== 删除评论(数组) ==========
function deleteComment(id) {
// ✅ 正确:filter 返回新数组
setComments(comments.filter(c => c.id !== id));
// ❌ 错误:comments.splice(index, 1); setComments(comments);
}
// 对象状态
let [user, setUser] = React.useState({
name: '小明',
age: 18,
city: '北京'
});
// ========== 修改对象属性 ==========
function updateAge() {
// ✅ 正确:展开运算符创建新对象
setUser({ ...user, age: user.age + 1 });
// ❌ 错误:user.age++; setUser(user);
}
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<h1>状态不可变性</h1>
{/* 用户信息 */}
<div style={{ padding: '16px', backgroundColor: '#f5f5f5', borderRadius: '8px', marginBottom: '20px' }}>
<p>姓名:{user.name},年龄:{user.age},城市:{user.city}</p>
<button onClick={updateAge}>🎂 长一岁</button>
</div>
{/* 评论列表 */}
<h2>评论 ({comments.length})</h2>
<button onClick={addComment} style={{ marginBottom: '10px' }}>
➕ 添加评论
</button>
{comments.map(comment => (
<div key={comment.id} style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '10px',
marginBottom: '6px',
border: '1px solid #eee',
borderRadius: '4px'
}}>
<span><strong>{comment.user}</strong>:{comment.text}</span>
<button onClick={() => deleteComment(comment.id)}
style={{ border: 'none', background: 'none', cursor: 'pointer', color: '#e74c3c' }}>
❌
</button>
</div>
))}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
不可变更新速查表
| 类型 | ❌ 错误(直接修改) | ✅ 正确(创建新值) |
|---|---|---|
| 数字 | count++ | setCount(count + 1) |
| 字符串 | name = '新名字' | setName('新名字') |
| 布尔 | isOn = !isOn | setIsOn(!isOn) |
| 数组添加 | arr.push(item) | setArr([...arr, item]) |
| 数组删除 | arr.splice(i, 1) | setArr(arr.filter(...)) |
| 数组修改 | arr[0] = x | setArr(arr.map(...)) |
| 对象修改 | obj.name = 'x' | setObj({'{'}...obj, name: 'x'{'}'}) |
⚠️ 新手易错:React 比较的是引用地址(是不是同一个对象),不是内容。直接修改原对象,地址没变,React 认为"没变化",不会重新渲染。所以必须创建新对象/新数组。
11.4 受控组件
受控组件就是输入框的值由 React 的 state 控制。输入框的值变了 → 更新 state → state 变了 → 输入框显示新值。
💻 完整示例代码(评论提交表单)
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>受控组件示例</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/babel">
// ========== 评论输入框 ==========
function CommentForm() {
let [comment, setComment] = React.useState('');
let [comments, setComments] = React.useState([]);
function handleSubmit(e) {
e.preventDefault(); // 阻止表单默认提交行为
if (comment.trim() === '') return; // 空评论不提交
// 添加到评论列表
setComments([...comments, {
id: Date.now(),
text: comment,
time: new Date().toLocaleTimeString()
}]);
// 清空输入框
setComment('');
}
return (
<div>
{/* 表单 */}
<form onSubmit={handleSubmit}>
<input
type="text"
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="写下你的评论..."
style={{
padding: '8px 12px',
width: '300px',
fontSize: '14px',
border: '1px solid #ddd',
borderRadius: '4px'
}}
/>
<button type="submit" style={{
marginLeft: '8px',
padding: '8px 16px',
backgroundColor: '#3498db',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}>
提交
</button>
</form>
{/* 实时预览 */}
{comment && (
<p style={{ color: '#999', fontSize: '13px', marginTop: '8px' }}>
预览:{comment}
</p>
)}
{/* 评论列表 */}
<div style={{ marginTop: '20px' }}>
{comments.map(c => (
<div key={c.id} style={{
padding: '10px',
marginBottom: '6px',
backgroundColor: '#f9f9f9',
borderRadius: '4px'
}}>
<p style={{ margin: 0 }}>{c.text}</p>
<small style={{ color: '#999' }}>{c.time}</small>
</div>
))}
</div>
</div>
);
}
function App() {
return (
<div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
<h1>受控组件:评论提交</h1>
<CommentForm />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
受控组件的工作流程
→ 触发 onChange 事件
→ e.target.value 拿到新值
→ setComment(新值) 更新 state
→ React 重新渲染,input 显示新值
受控 vs 非受控
| 类型 | 值的来源 | 特点 |
|---|---|---|
| 受控组件 | React 的 state | React 完全控制,推荐 |
| 非受控组件 | DOM 本身 | 用 ref 取值,不推荐 |
⚠️ 新手易错:
onChange在 React 中每次输入都会触发,不是失焦才触发(和原生 HTML 的onchange不同)。
⚠️ 新手易错:
e.preventDefault()在表单提交时必须调用,否则浏览器会刷新页面(这是 HTML 表单的默认行为)。
本章复盘——State 与事件处理核心要点
| 要点 | 说明 |
|---|---|
| useState | 函数组件中管理状态的核心 Hook |
| 事件处理 | onClick 等事件绑定,直接传函数引用 |
| 状态更新 | setState 是异步的,批量更新优化性能 |
| 受控组件 | 表单元素的 value 由 React state 控制 |
下一章预告(第14章):我们将学习Next.js 快速上手——React 全栈框架入门、项目创建和基础配置。
本文档由 zyyc-0336 编写,最后更新:2026-06-14