事件处理 #
一、事件基础 #
1.1 React事件特点 #
React 事件是合成事件(SyntheticEvent),是对原生事件的跨浏览器封装。
| 特点 | 说明 |
|---|---|
| 驼峰命名 | onClick 而非 onclick |
| 函数引用 | 传递函数而非字符串 |
| 事件委托 | 默认委托到根节点 |
| 跨浏览器 | 统一的事件对象 |
1.2 基本用法 #
javascript
function Button() {
const handleClick = () => {
console.log('按钮被点击');
};
return <button onClick={handleClick}>点击我</button>;
}
1.3 与原生事件对比 #
javascript
// 原生HTML事件
<button onclick="handleClick()">点击</button>
// React事件
<button onClick={handleClick}>点击</button>
二、事件绑定方式 #
2.1 函数组件中的绑定 #
javascript
function Counter() {
const [count, setCount] = useState(0);
// 方式一:箭头函数
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
</div>
);
}
2.2 类组件中的绑定 #
javascript
class Counter extends Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// 方式一:在构造函数中绑定
this.increment = this.increment.bind(this);
}
// 方式一:需要绑定this
increment() {
this.setState({ count: this.state.count + 1 });
}
// 方式二:类字段语法(推荐)
decrement = () => {
this.setState({ count: this.state.count - 1 });
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increment}>增加</button>
<button onClick={this.decrement}>减少</button>
{/* 方式三:箭头函数 */}
<button onClick={() => this.setState({ count: 0 })}>重置</button>
</div>
);
}
}
2.3 绑定方式对比 #
| 方式 | 性能 | 推荐程度 |
|---|---|---|
| 构造函数绑定 | 优 | 可用 |
| 类字段语法 | 优 | 推荐 |
| 箭头函数回调 | 较差 | 简单场景可用 |
三、事件对象 #
3.1 访问事件对象 #
javascript
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单提交');
};
const handleClick = (e) => {
console.log('事件类型:', e.type);
console.log('目标元素:', e.target);
console.log('当前元素:', e.currentTarget);
};
return (
<form onSubmit={handleSubmit}>
<button type="submit" onClick={handleClick}>提交</button>
</form>
);
}
3.2 常用事件属性 #
javascript
function MouseEvent() {
const handleClick = (e) => {
console.log('鼠标位置:', e.clientX, e.clientY);
console.log('按键:', e.button);
console.log('是否按住Ctrl:', e.ctrlKey);
};
return <div onClick={handleClick}>点击我</div>;
}
function KeyboardEvent() {
const handleKeyDown = (e) => {
console.log('按键:', e.key);
console.log('键码:', e.keyCode);
console.log('是否按住Shift:', e.shiftKey);
};
return <input onKeyDown={handleKeyDown} />;
}
3.3 阻止默认行为 #
javascript
function Link() {
const handleClick = (e) => {
e.preventDefault();
console.log('链接被点击,但不会跳转');
};
return (
<a href="https://example.com" onClick={handleClick}>
点击我
</a>
);
}
3.4 阻止事件冒泡 #
javascript
function EventPropagation() {
const handleParentClick = () => {
console.log('父元素点击');
};
const handleChildClick = (e) => {
e.stopPropagation();
console.log('子元素点击');
};
return (
<div onClick={handleParentClick} style={{ padding: '20px', background: '#f0f0f0' }}>
<button onClick={handleChildClick}>子按钮</button>
</div>
);
}
四、传递参数 #
4.1 箭头函数方式 #
javascript
function ItemList({ items }) {
const handleDelete = (id, event) => {
console.log('删除:', id);
console.log('事件:', event);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={(e) => handleDelete(item.id, e)}>
删除
</button>
</li>
))}
</ul>
);
}
4.2 bind方式 #
javascript
class ItemList extends Component {
handleDelete(id, event) {
console.log('删除:', id);
console.log('事件:', event);
}
render() {
const { items } = this.props;
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={this.handleDelete.bind(this, item.id)}>
删除
</button>
</li>
))}
</ul>
);
}
}
4.3 使用data属性 #
javascript
function ItemList({ items }) {
const handleClick = (e) => {
const id = e.currentTarget.dataset.id;
console.log('ID:', id);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button data-id={item.id} onClick={handleClick}>
点击
</button>
</li>
))}
</ul>
);
}
五、常见事件类型 #
5.1 表单事件 #
javascript
function FormExample() {
const [formData, setFormData] = useState({
username: '',
email: '',
message: ''
});
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('提交数据:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
name="username"
value={formData.username}
onChange={handleChange}
/>
</div>
<div>
<label>邮箱:</label>
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label>消息:</label>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
/>
</div>
<button type="submit">提交</button>
</form>
);
}
5.2 鼠标事件 #
javascript
function MouseEvents() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
const handleMouseEnter = () => {
console.log('鼠标进入');
};
const handleMouseLeave = () => {
console.log('鼠标离开');
};
return (
<div
onMouseMove={handleMouseMove}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{ width: '300px', height: '200px', background: '#f0f0f0' }}
>
<p>X: {position.x}, Y: {position.y}</p>
</div>
);
}
5.3 键盘事件 #
javascript
function KeyboardEvents() {
const [key, setKey] = useState('');
const handleKeyDown = (e) => {
console.log('按下:', e.key);
if (e.key === 'Enter') {
console.log('回车键');
}
if (e.key === 'Escape') {
console.log('ESC键');
}
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
console.log('Ctrl+S 保存');
}
};
const handleKeyUp = (e) => {
setKey(e.key);
};
return (
<div>
<input
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
placeholder="按下键盘"
/>
<p>最后释放的键: {key}</p>
</div>
);
}
5.4 焦点事件 #
javascript
function FocusEvents() {
const [focused, setFocused] = useState(false);
return (
<input
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
style={{ border: focused ? '2px solid blue' : '1px solid gray' }}
placeholder={focused ? '已聚焦' : '点击聚焦'}
/>
);
}
5.5 剪贴板事件 #
javascript
function ClipboardEvents() {
const handleCopy = (e) => {
console.log('复制');
};
const handlePaste = (e) => {
const text = e.clipboardData.getData('text');
console.log('粘贴:', text);
};
return (
<input
onCopy={handleCopy}
onPaste={handlePaste}
placeholder="尝试复制粘贴"
/>
);
}
六、事件委托 #
6.1 原理 #
React 使用事件委托,将事件绑定到根节点:
javascript
function List() {
const handleClick = (e) => {
const target = e.target;
if (target.tagName === 'BUTTON') {
const action = target.dataset.action;
const id = target.dataset.id;
switch (action) {
case 'edit':
console.log('编辑:', id);
break;
case 'delete':
console.log('删除:', id);
break;
}
}
};
return (
<ul onClick={handleClick}>
{items.map(item => (
<li key={item.id}>
{item.name}
<button data-action="edit" data-id={item.id}>编辑</button>
<button data-action="delete" data-id={item.id}>删除</button>
</li>
))}
</ul>
);
}
6.2 React 17+的变化 #
React 17 之前,事件委托到 document;React 17+,事件委托到根容器。
javascript
// React 17+
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// 事件绑定到 #root 元素
七、合成事件 #
7.1 什么是合成事件 #
合成事件是 React 对原生事件的封装:
javascript
function SyntheticEventDemo() {
const handleClick = (e) => {
console.log('合成事件:', e);
console.log('原生事件:', e.nativeEvent);
console.log('事件类型:', e.type);
console.log('目标元素:', e.target);
};
return <button onClick={handleClick}>点击</button>;
}
7.2 事件池 #
React 17 之前,合成事件会被池化复用:
javascript
// React 16及之前
function handleClick(e) {
// e.persist(); // 需要调用persist才能异步访问
setTimeout(() => {
console.log(e.type); // null,事件已被回收
}, 0);
}
// React 17+,不再池化
function handleClick(e) {
setTimeout(() => {
console.log(e.type); // 正常访问
}, 0);
}
八、最佳实践 #
8.1 避免内联函数(性能敏感场景) #
javascript
// ❌ 每次渲染创建新函数
function List({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => console.log(item.id)}>
{item.name}
</li>
))}
</ul>
);
}
// ✅ 使用useCallback
function List({ items }) {
const handleClick = useCallback((id) => {
console.log(id);
}, []);
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</li>
))}
</ul>
);
}
8.2 使用事件处理函数命名约定 #
javascript
// ✅ 好的命名
function Form() {
const handleInputChange = (e) => { };
const handleSubmit = (e) => { };
const handleReset = () => { };
const handleButtonClick = () => { };
return <form onSubmit={handleSubmit}>...</form>;
}
8.3 防抖和节流 #
javascript
import { useState, useCallback } from 'react';
import { debounce, throttle } from 'lodash';
function SearchInput() {
const [results, setResults] = useState([]);
// 防抖搜索
const handleSearch = useCallback(
debounce((query) => {
fetchResults(query).then(setResults);
}, 300),
[]
);
// 节流滚动
const handleScroll = useCallback(
throttle(() => {
console.log('滚动位置:', window.scrollY);
}, 100),
[]
);
return (
<div onScroll={handleScroll}>
<input onChange={(e) => handleSearch(e.target.value)} />
<ul>
{results.map(result => (
<li key={result.id}>{result.name}</li>
))}
</ul>
</div>
);
}
8.4 键盘可访问性 #
javascript
function Button({ onClick, children }) {
const handleKeyDown = (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onClick();
}
};
return (
<div
role="button"
tabIndex={0}
onClick={onClick}
onKeyDown={handleKeyDown}
>
{children}
</div>
);
}
九、常见问题 #
9.1 this绑定问题 #
javascript
// ❌ 类组件中this丢失
class Button extends Component {
handleClick() {
console.log(this); // undefined
}
render() {
return <button onClick={this.handleClick}>点击</button>;
}
}
// ✅ 解决方案
class Button extends Component {
handleClick = () => {
console.log(this); // Button实例
};
render() {
return <button onClick={this.handleClick}>点击</button>;
}
}
9.2 事件对象异步访问 #
javascript
// React 16及之前
function handleClick(e) {
e.persist(); // 保持事件对象
setTimeout(() => {
console.log(e.target); // 可以访问
}, 0);
}
// React 17+,不需要persist
function handleClick(e) {
setTimeout(() => {
console.log(e.target); // 可以直接访问
}, 0);
}
十、总结 #
| 要点 | 说明 |
|---|---|
| 驼峰命名 | onClick, onSubmit |
| 函数引用 | 传递函数而非字符串 |
| 事件对象 | 合成事件,跨浏览器兼容 |
| 参数传递 | 箭头函数或bind |
核心原则:
- 使用驼峰命名事件
- 类组件注意 this 绑定
- 使用 e.preventDefault() 阻止默认行为
- 使用 e.stopPropagation() 阻止冒泡
- 性能敏感场景使用 useCallback
最后更新:2026-03-26