react
事件处理与绑定
By AI-Writer 6 min read
前言
交互式应用离不开事件处理。React 封装了浏览器的原生事件,形成了**合成事件(SyntheticEvent)**系统,提供了一套跨浏览器兼容、行为一致的 API。本文将详细介绍 React 事件处理的各个方面。
合成事件系统
什么是合成事件
React 在所有支持的浏览器中,为你提供相同接口的跨浏览器事件对象——这就是合成事件。它整合了浏览器的原生事件,并在其上构建了统一的 API:
jsx
function EventDemo() {
function handleClick(event) {
// event 是一个合成事件,跨浏览器兼容
console.log('点击了!');
console.log('事件类型:', event.type); // click
console.log('触发元素:', event.currentTarget); // 绑定事件的元素
console.log('原生事件:', event.nativeEvent); // 原始浏览器事件
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
}
return <button onClick={handleClick}>点击我</button>;
}合成事件 vs 原生事件
| 特性 | 合成事件(SyntheticEvent) | 原生事件(NativeEvent) |
|---|---|---|
| 跨浏览器兼容 | ✓ 自动兼容 | ✗ 需要处理兼容性 |
| 事件池 | ✓ 可复用,事件对象被池化 | ✗ 每事件一个对象 |
| 自动绑定 | ✓ 事件处理器中 this 指向组件实例(类组件) | ✗ 需要手动绑定 |
| 卸载时自动清理 | ✓ 自动解绑 | ✗ 需手动清理 |
事件绑定方式
在函数组件中绑定事件
函数组件中,事件处理器就是普通的 JavaScript 函数,因此不需要处理 this 绑定问题:
jsx
function FunctionEventDemo() {
// 方式1:箭头函数(直接定义)
const handleClick = () => {
console.log('点击了');
};
// 方式2:普通函数
function handleMouseEnter() {
console.log('鼠标进入');
}
return (
<div>
<button onClick={handleClick}>按钮1</button>
<div onMouseEnter={handleMouseEnter}>悬停区域</div>
</div>
);
}事件处理器的内联写法
对于简单逻辑,可以直接在 JSX 中内联定义:
jsx
function InlineEventDemo() {
return (
<div>
{/* 内联箭头函数 */}
<button onClick={() => console.log('点击1')}>按钮1</button>
{/* 访问外部变量 */}
<button onClick={() => alert('Hello!')}>弹出提示</button>
{/* 调用带参数的函数 */}
<button onClick={() => handleAction('save')}>保存</button>
<button onClick={() => handleAction('delete')}>删除</button>
</div>
);
}
function handleAction(action) {
console.log(`执行操作: ${action}`);
}向事件处理器传参
方式一:箭头函数包装
jsx
function ArgDemo() {
function deleteItem(id) {
console.log(`删除 ID: ${id}`);
}
return (
<div>
{/* ❌ 直接传参:事件对象被作为参数,id 丢失 */}
<button onClick={deleteItem(1)}>错误写法</button>
{/* ✓ 箭头函数包装 */}
<button onClick={() => deleteItem(1)}>删除项目1</button>
<button onClick={() => deleteItem(2)}>删除项目2</button>
{/* ✓ 同时传递事件对象和额外参数 */}
<button onClick={(e) => {
console.log('事件类型:', e.type);
deleteItem(3);
}}>
带事件信息的删除
</button>
</div>
);
}方式二:闭包捕获
jsx
function ClosureDemo() {
// 创建带参数的事件处理器
const createHandler = (userId, action) => (e) => {
console.log(`用户 ${userId} 执行了 ${action}`);
console.log('触发元素:', e.currentTarget.textContent);
};
return (
<div>
<button onClick={createHandler(1, '编辑')}>用户1-编辑</button>
<button onClick={createHandler(2, '删除')}>用户2-删除</button>
</div>
);
}常用事件类型
鼠标事件
jsx
function MouseEvents() {
const [hoverCount, setHoverCount] = useState(0);
return (
<div
onClick={() => console.log('点击')}
onDoubleClick={() => console.log('双击')}
onMouseEnter={() => setHoverCount(c => c + 1)}
onMouseLeave={() => console.log('鼠标离开')}
onMouseMove={(e) => console.log(`位置: ${e.clientX}, ${e.clientY}`)}
style={{ padding: '2rem', background: '#f0f0f0', cursor: 'pointer' }}
>
<p>鼠标进入次数:{hoverCount}</p>
<p>在区域内移动查看坐标</p>
</div>
);
}表单事件
jsx
function FormEvents() {
const [text, setText] = useState('');
const [selected, setSelected] = useState('vue');
function handleSubmit(e) {
e.preventDefault(); // 阻止表单默认提交行为
console.log('提交:', { text, selected });
}
function handleChange(e) {
setText(e.target.value);
}
function handleSelectChange(e) {
setSelected(e.target.value);
}
return (
<form onSubmit={handleSubmit}>
<div>
<label>
名称:
<input
type="text"
value={text}
onChange={handleChange}
placeholder="输入名称"
/>
</label>
<p>输入内容:{text}</p>
</div>
<div>
<label>
框架:
<select value={selected} onChange={handleSelectChange}>
<option value="react">React</option>
<option value="vue">Vue</option>
<option value="angular">Angular</option>
</select>
</label>
<p>选择:{selected}</p>
</div>
<button type="submit">提交表单</button>
</form>
);
}键盘事件
jsx
function KeyboardEvents() {
const [log, setLog] = useState([]);
function handleKeyDown(e) {
const entry = {
key: e.key,
code: e.code,
ctrl: e.ctrlKey,
shift: e.shiftKey,
time: new Date().toLocaleTimeString()
};
setLog(prev => [entry, ...prev].slice(0, 10)); // 保留最近10条
}
return (
<div>
<input
onKeyDown={handleKeyDown}
placeholder="在此输入,查看按键日志..."
style={{ padding: '0.5rem', width: '100%', fontSize: '1rem' }}
/>
<div style={{ marginTop: '1rem', fontFamily: 'monospace', fontSize: '0.875rem' }}>
{log.map((entry, i) => (
<div key={i}>
[{entry.time}] key={entry.key.padEnd(8)} code={entry.code.padEnd(20)}
{entry.ctrl && ' [Ctrl]'}
{entry.shift && ' [Shift]'}
</div>
))}
</div>
</div>
);
}焦点事件
jsx
function FocusEvents() {
const [status, setStatus] = useState('未聚焦');
return (
<div>
<input
type="text"
onFocus={() => setStatus('已聚焦')}
onBlur={() => setStatus('已失焦')}
placeholder="点击这里..."
/>
<p>状态:{status}</p>
</div>
);
}事件委托机制
React 如何处理事件
React 16 及之前:事件委托到 document 根节点
React 17+:事件委托到挂载到的 DOM 根节点(支持多 React 版本共存)
jsx
// React 17+ 的事件委托
// 事件监听器被附加到 React 应用的根 DOM 节点,而非 document
function EventDelegation() {
return (
<div id="root">
{/* 点击任何按钮,事件冒泡到根节点,由 React 统一处理 */}
<button onClick={() => alert('按钮1')}>按钮1</button>
<button onClick={() => alert('按钮2')}>按钮2</button>
<button onClick={() => alert('按钮3')}>按钮3</button>
</div>
);
}手动事件监听器
在某些场景下(如监听第三方库的 DOM 变化),需要手动添加事件监听器。使用 useEffect 和清理函数:
jsx
import { useEffect, useState } from 'react';
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
// 添加事件监听
window.addEventListener('resize', handleResize);
// 清理函数:组件卸载时移除监听器
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // 空依赖数组:仅在挂载/卸载时执行
return size;
}
function WindowSizeDisplay() {
const { width, height } = useWindowSize();
return (
<div>
<p>窗口宽度:{width}px</p>
<p>窗口高度:{height}px</p>
</div>
);
}事件对象的常用属性
jsx
function EventProperties() {
function handleEvent(e) {
// 事件基础属性
console.log('type:', e.type); // 事件类型
console.log('target:', e.target); // 触发事件的元素
console.log('currentTarget:', e.currentTarget); // 绑定事件的元素
// 键盘事件专属
// e.key - 按下的键值('a', 'Enter', 'ArrowUp')
// e.code - 物理键码('KeyA', 'Enter', 'ArrowUp')
// e.ctrlKey - Ctrl 键是否按下
// e.shiftKey - Shift 键是否按下
// e.altKey - Alt 键是否按下
// 鼠标事件专属
// e.clientX / e.clientY - 相对于视口的坐标
// e.pageX / e.pageY - 相对于页面的坐标
// e.button - 按下的鼠标按钮(0=左, 1=中, 2=右)
// 表单事件专属
// e.target.value - 输入值
// e.target.checked - 复选框/单选框状态
}
return (
<div>
<input onKeyDown={handleEvent} placeholder="按任意键" />
<button onClick={handleEvent}>点击</button>
</div>
);
}完整示例:计数器(事件驱动版)
jsx
import { useState } from 'react';
function EventCounter() {
const [count, setCount] = useState(0);
const [history, setHistory] = useState([]);
function handleIncrement() {
setCount(prev => prev + 1);
setHistory(prev => [...prev, { op: '+', time: Date.now() }]);
}
function handleDecrement() {
setCount(prev => prev - 1);
setHistory(prev => [...prev, { op: '-', time: Date.now() }]);
}
function handleReset() {
setCount(0);
setHistory([]);
}
function handleUndo() {
if (history.length === 0) return;
const last = history[history.length - 1];
setHistory(prev => prev.slice(0, -1));
if (last.op === '+') {
setCount(prev => prev - 1);
} else {
setCount(prev => prev + 1);
}
}
return (
<div style={{ maxWidth: 320, margin: '2rem auto', padding: '1.5rem', border: '3px solid #121212', boxShadow: '6px 6px 0 0 #121212' }}>
<h2 style={{ textAlign: 'center', margin: '0 0 1rem' }}>事件计数器</h2>
<div style={{ fontSize: '3rem', fontWeight: 'bold', textAlign: 'center', padding: '1rem', background: '#F0C020', color: '#121212', marginBottom: '1rem' }}>
{count}
</div>
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center', marginBottom: '1rem' }}>
<button
onClick={handleDecrement}
style={{ padding: '0.5rem 1.5rem', fontSize: '1.25rem', fontWeight: 'bold', background: '#1040C0', color: 'white', border: '2px solid #121212', cursor: 'pointer' }}
>
−
</button>
<button
onClick={handleReset}
style={{ padding: '0.5rem 1rem', fontWeight: 'bold', background: '#F0C020', color: '#121212', border: '2px solid #121212', cursor: 'pointer' }}
>
重置
</button>
<button
onClick={handleIncrement}
style={{ padding: '0.5rem 1.5rem', fontSize: '1.25rem', fontWeight: 'bold', background: '#D02020', color: 'white', border: '2px solid #121212', cursor: 'pointer' }}
>
+
</button>
</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<button
onClick={handleUndo}
disabled={history.length === 0}
style={{ padding: '0.5rem 1rem', opacity: history.length === 0 ? 0.4 : 1, cursor: history.length === 0 ? 'not-allowed' : 'pointer', border: '2px solid #121212' }}
>
撤销
</button>
</div>
{history.length > 0 && (
<div style={{ marginTop: '1rem', fontSize: '0.875rem', color: '#666' }}>
最近操作:{history[history.length - 1]?.op === '+' ? '+1' : '-1'}
</div>
)}
</div>
);
}
export default EventCounter;小结
- 合成事件:React 封装了浏览器的原生事件,提供跨浏览器兼容的 API
- 事件绑定:函数组件中直接传递函数引用,无需处理
this绑定 - 传参方式:用箭头函数包装
() => handler(param),或使用闭包 - 常用事件:
onClick、onChange、onSubmit、onKeyDown、onFocus等 - 事件委托:React 将事件监听器附加到根节点,而非每个元素,提高性能
- 手动清理:
useEffect中添加的事件监听器、定时器,必须在清理函数中移除
掌握事件处理后,接下来我们将学习 React 的条件渲染与列表渲染,了解如何根据状态动态控制 UI 的显示与隐藏。
#react
#事件
#synthetic-event
#入门
评论
A
Written by
AI-Writer
Related Articles
react
#1 React 元素与 JSX 语法
深入理解 JSX 的本质——JavaScript 的语法扩展,掌握虚拟 DOM 概念、JSX 编译原理以及开发中常见的错误与最佳实践
Read More react
#12 Vitest + Testing Library 测试
掌握 Vitest 快速测试运行器和 React Testing Library 的组件测试、用户交互模拟、Mock 技巧与测试覆盖率实践
Read More