React 事件处理
概述
React 提供了一套强大的事件处理系统,让我们能够响应用户的各种交互操作。本章将学习 React 事件的工作原理、事件处理器的编写方法、事件对象的使用,以及各种常见的用户交互场景。
🎯 React 事件系统
SyntheticEvent(合成事件)
jsx
function EventBasics() {
const handleClick = (event) => {
console.log('事件类型:', event.type);
console.log('目标元素:', event.target);
console.log('当前元素:', event.currentTarget);
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
};
return (
<div>
<button onClick={handleClick}>点击我</button>
<a href="#" onClick={handleClick}>
点击链接(默认行为被阻止)
</a>
</div>
);
}事件绑定方式
jsx
// 函数组件推荐方式
function ModernEventBinding() {
const [count, setCount] = React.useState(0);
// 推荐:使用 useCallback 优化性能
const handleClick = React.useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>计数: {count}</p>
<button onClick={handleClick}>增加</button>
</div>
);
}
// 类组件事件绑定
class ClassEventBinding extends React.Component {
state = { count: 0 };
// 推荐:箭头函数属性
handleClick = () => {
this.setState(prev => ({ count: prev.count + 1 }));
};
render() {
return (
<div>
<p>计数: {this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
</div>
);
}
}🖱️ 鼠标事件
基础鼠标事件
jsx
function MouseEvents() {
const [position, setPosition] = React.useState({ x: 0, y: 0 });
const [clicked, setClicked] = React.useState(false);
const handleMouseMove = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
const handleClick = () => {
setClicked(true);
setTimeout(() => setClicked(false), 1000);
};
return (
<div
onMouseMove={handleMouseMove}
style={{ height: '200px', border: '1px solid #ccc', padding: '20px' }}
>
<p>鼠标位置: ({position.x}, {position.y})</p>
<button
onClick={handleClick}
style={{ backgroundColor: clicked ? 'green' : 'blue', color: 'white' }}
>
{clicked ? '已点击!' : '点击我'}
</button>
</div>
);
}拖拽功能
jsx
function DragAndDrop() {
const [items, setItems] = React.useState([
{ id: 1, text: '拖拽我', x: 0, y: 0 }
]);
const [dragging, setDragging] = React.useState(null);
const handleMouseDown = (event, item) => {
setDragging({
id: item.id,
offsetX: event.clientX - item.x,
offsetY: event.clientY - item.y
});
};
const handleMouseMove = (event) => {
if (dragging) {
setItems(items.map(item =>
item.id === dragging.id
? {
...item,
x: event.clientX - dragging.offsetX,
y: event.clientY - dragging.offsetY
}
: item
));
}
};
const handleMouseUp = () => {
setDragging(null);
};
React.useEffect(() => {
if (dragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}
}, [dragging]);
return (
<div style={{ position: 'relative', height: '300px', border: '1px solid #ccc' }}>
{items.map(item => (
<div
key={item.id}
onMouseDown={(e) => handleMouseDown(e, item)}
style={{
position: 'absolute',
left: item.x,
top: item.y,
padding: '10px',
backgroundColor: 'lightblue',
cursor: 'move',
userSelect: 'none'
}}
>
{item.text}
</div>
))}
</div>
);
}⌨️ 键盘事件
键盘输入处理
jsx
function KeyboardEvents() {
const [input, setInput] = React.useState('');
const [history, setHistory] = React.useState([]);
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
if (input.trim()) {
setHistory([...history, input]);
setInput('');
}
} else if (event.key === 'Escape') {
setInput('');
}
};
return (
<div>
<input
type="text"
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="输入内容,按 Enter 提交,Escape 清空"
style={{ width: '300px', padding: '10px' }}
/>
<h3>历史记录:</h3>
<ul>
{history.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}📝 表单事件
表单控件事件
jsx
function FormEvents() {
const [formData, setFormData] = React.useState({
name: '',
email: '',
gender: '',
agree: false
});
const handleChange = (event) => {
const { name, value, type, checked } = event.target;
setFormData({
...formData,
[name]: type === 'checkbox' ? checked : value
});
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('表单数据:', formData);
alert('表单提交成功!');
};
return (
<form onSubmit={handleSubmit} style={{ maxWidth: '400px' }}>
<div style={{ marginBottom: '15px' }}>
<label>姓名:</label>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label>邮箱:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
style={{ width: '100%', padding: '8px' }}
/>
</div>
<div style={{ marginBottom: '15px' }}>
<label>性别:</label>
<select name="gender" value={formData.gender} onChange={handleChange}>
<option value="">请选择</option>
<option value="male">男</option>
<option value="female">女</option>
</select>
</div>
<div style={{ marginBottom: '15px' }}>
<label>
<input
type="checkbox"
name="agree"
checked={formData.agree}
onChange={handleChange}
/>
同意条款
</label>
</div>
<button type="submit" disabled={!formData.agree}>
提交
</button>
</form>
);
}⚡ 事件性能优化
防抖和节流
jsx
function EventOptimization() {
const [searchTerm, setSearchTerm] = React.useState('');
const [debouncedTerm, setDebouncedTerm] = React.useState('');
// 防抖处理
React.useEffect(() => {
const timer = setTimeout(() => {
setDebouncedTerm(searchTerm);
}, 300);
return () => clearTimeout(timer);
}, [searchTerm]);
// 节流处理
const [scrollPosition, setScrollPosition] = React.useState(0);
const throttledScroll = React.useCallback(
React.useMemo(() => {
let lastRun = 0;
return (event) => {
if (Date.now() - lastRun >= 100) {
setScrollPosition(event.target.scrollTop);
lastRun = Date.now();
}
};
}, []),
[]
);
return (
<div>
<h3>搜索(防抖)</h3>
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="输入搜索内容..."
/>
<p>搜索词:{debouncedTerm}</p>
<h3>滚动(节流)</h3>
<div
onScroll={throttledScroll}
style={{ height: '100px', overflow: 'auto', border: '1px solid #ccc' }}
>
<div style={{ height: '300px', padding: '10px' }}>
滚动这个区域查看位置信息
<br />滚动位置:{scrollPosition}
</div>
</div>
</div>
);
}🎪 事件委托
高效事件处理
jsx
function EventDelegation() {
const [items, setItems] = React.useState([
{ id: 1, name: '项目 1', active: false },
{ id: 2, name: '项目 2', active: false },
{ id: 3, name: '项目 3', active: false }
]);
// 事件委托处理
const handleContainerClick = (event) => {
const button = event.target.closest('button');
if (button) {
const action = button.dataset.action;
const itemId = parseInt(button.dataset.itemId);
if (action === 'toggle') {
setItems(prev =>
prev.map(item =>
item.id === itemId ? { ...item, active: !item.active } : item
)
);
} else if (action === 'delete') {
setItems(prev => prev.filter(item => item.id !== itemId));
}
}
};
const addItem = () => {
const newId = Math.max(...items.map(item => item.id)) + 1;
setItems([...items, { id: newId, name: `项目 ${newId}`, active: false }]);
};
return (
<div onClick={handleContainerClick}>
<button onClick={addItem} style={{ marginBottom: '10px' }}>
添加项目
</button>
{items.map(item => (
<div
key={item.id}
style={{
padding: '10px',
margin: '5px 0',
border: '1px solid #ddd',
backgroundColor: item.active ? '#e7f3ff' : '#f8f9fa'
}}
>
{item.name} {item.active ? '(激活)' : ''}
<button
data-action="toggle"
data-item-id={item.id}
style={{ marginLeft: '10px' }}
>
切换状态
</button>
<button
data-action="delete"
data-item-id={item.id}
style={{ marginLeft: '5px', color: 'red' }}
>
删除
</button>
</div>
))}
</div>
);
}📝 本章小结
通过本章学习,你应该掌握了:
核心概念
- ✅ React 合成事件的工作原理
- ✅ 事件对象的属性和方法
- ✅ 事件绑定的最佳实践
- ✅ 事件冒泡和默认行为控制
事件类型
- ✅ 鼠标事件:点击、移动、拖拽
- ✅ 键盘事件:按键处理、快捷键
- ✅ 表单事件:输入、提交、验证
- ✅ 触摸事件:移动端交互
性能优化
- ✅ 事件委托减少监听器数量
- ✅ 防抖和节流优化频繁事件
- ✅ useCallback 缓存事件处理器
- ✅ 合理的事件绑定时机
最佳实践
- 使用合成事件:兼容性好,功能完整
- 避免内联函数:使用 useCallback 优化
- 合理使用事件委托:减少内存占用
- 处理清理逻辑:防止内存泄漏
- 性能监控:关注事件处理性能
继续学习:下一章 - React 条件和列表渲染