第11章 State与事件处理

让组件能够响应用户操作并更新界面

作者:zyyc-0336  |  日期:2026-06-12

📚 学习路径建议

  1. 先学 11.1(事件绑定),掌握 React 中如何响应用户操作
  2. 再学 11.2-11.3(useState 和状态不可变性),理解 React 的"数据驱动 UI"
  3. 最后学 11.4(受控组件),学会在 React 中处理表单
⚠️ 常见错误提示
  • ❌ 不要直接修改 state:count++ ❌,必须用 setCount(count + 1)
  • useState 只能在组件的最顶层调用,不能在 if/for/函数里
  • 事件名用驼峰命名onClick 不是 onclickonChange 不是 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 写法说明
点击onClickonclick按钮点击
双击onDoubleClickondblclick双击
鼠标移入onMouseEnteronmouseenter鼠标进入
鼠标移出onMouseLeaveonmouseleave鼠标离开
输入变化onChangeonchange输入框内容变化
表单提交onSubmitonsubmit表单提交
键盘按下onKeyDownonkeydown按键

⚠️ 新手易错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 = !isOnsetIsOn(!isOn)
数组添加arr.push(item)setArr([...arr, item])
数组删除arr.splice(i, 1)setArr(arr.filter(...))
数组修改arr[0] = xsetArr(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 的 stateReact 完全控制,推荐
非受控组件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