事件处理
响应⽤户的交互操作,如点击、输⼊、提交等
什么是事件处理?
事件处理函数是响应用户操作的函数,如点击按钮、在输入框中输入文字、提交表单等。在 React 中,事件处理通过 JSX 属性来定义。
TypeScript
function Button() {
const handleClick = () => {
console.log('按钮被点击了!');
};
return (
<button onClick={handleClick}>
点击我
</button>
);
}
核心概念
React 事件使用驼峰命名(camelCase),而不是小写。 事件处理函数作为 props 传递,而不是字符串。
添加事件处理函数
定义处理函数
TypeScript
function Button() {
// 在组件内部定义
function handleClick() {
console.log('点击!');
}
return <button onClick={handleClick}>点击</button>;
}
内联处理函数
TypeScript
function Button() {
return (
<button onClick={() => console.log('点击!')}>
点击
</button>
);
}
直接传递函数
TypeScript
function Button() {
return (
<button onClick={function() { console.log('点击!'); }}>
点击
</button>
);
}
常见错误
不要在 JSX 中调用函数:
TypeScript
// ❌ 错误:立即执行函数
<button onClick={handleClick()}>
// ✅ 正确:传递函数引用
<button onClick={handleClick}>
事件对象
访问事件对象
TypeScript
function Button() {
const handleClick = (e) => {
e.preventDefault();
console.log('事件对象:', e);
console.log('目标元素:', e.target);
console.log('当前元素:', e.currentTarget);
};
return <button onClick={handleClick}>点击</button>;
}
常用的事件属性
TypeScript
function Form() {
const handleSubmit = (e) => {
// 阻止默认行为(如表单提交)
e.preventDefault();
// 阻止事件冒泡
e.stopPropagation();
// 获取事件目标
const target = e.target;
// 获取目标值(输入框)
const value = target.value;
console.log('表单提交:', value);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="username" />
<button type="submit">提交</button>
</form>
);
}
TypeScript 类型
TypeScript
import { MouseEvent, ChangeEvent, FormEvent } from 'react';
function Button() {
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
console.log(e.currentTarget);
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value);
};
const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
return (
<form onSubmit={handleSubmit}>
<input type="text" onChange={handleChange} />
<button onClick={handleClick}>点击</button>
</form>
);
}
传递参数
传递额外参数
TypeScript
function ButtonList() {
const buttons = [
{ id: 1, label: '按钮 1' },
{ id: 2, label: '按钮 2' },
{ id: 3, label: '按钮 3' },
];
const handleClick = (buttonId) => {
console.log('按钮 ID:', buttonId);
};
return (
<div>
{buttons.map(button => (
<button
key={button.id}
onClick={() => handleClick(button.id)}
>
{button.label}
</button>
))}
</div>
);
}
同时传递事件和参数
TypeScript
function ButtonList() {
const handleClick = (e, buttonId) => {
e.preventDefault();
console.log('按钮 ID:', buttonId);
console.log('事件对象:', e);
};
return (
<div>
{buttons.map(button => (
<button
key={button.id}
onClick={(e) => handleClick(e, button.id)}
>
{button.label}
</button>
))}
</div>
);
}
使用 bind
TypeScript
function ButtonList() {
const handleClick = function(buttonId, e) {
e.preventDefault();
console.log('按钮 ID:', buttonId);
};
return (
<div>
{buttons.map(button => (
<button
key={button.id}
onClick={handleClick.bind(null, button.id)}
>
{button.label}
</button>
))}
</div>
);
}
推荐方式
使用箭头函数是最常见和推荐的方式,因为它更简洁易读。 只在高性能要求的场景下考虑其他方式。
常见事件类型
鼠标事件
TypeScript
function MouseEvents() {
return (
<div
onClick={() => console.log('单击')}
onDoubleClick={() => console.log('双击')}
onMouseEnter={() => console.log('鼠标进入')}
onMouseLeave={() => console.log('鼠标离开')}
onMouseMove={() => console.log('鼠标移动')}
onMouseDown={() => console.log('鼠标按下')}
onMouseUp={() => console.log('鼠标释放')}
style={{ padding: '20px', border: '1px solid black' }}
>
鼠标事件区域
</div>
);
}
键盘事件
TypeScript
function KeyboardEvents() {
const handleKeyDown = (e) => {
console.log('按键:', e.key);
console.log('代码:', e.code);
// 检测特定键
if (e.key === 'Enter') {
console.log('回车键被按下');
}
// 检测组合键
if (e.ctrlKey && e.key === 's') {
e.preventDefault();
console.log('Ctrl+S 被按下');
}
};
return (
<input
type="text"
onKeyDown={handleKeyDown}
onKeyUp={(e) => console.log('按键释放:', e.key)}
onKeyPress={(e) => console.log('按键字符:', e.key)}
placeholder="输入文字..."
/>
);
}
表单事件
TypeScript
function FormEvents() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
const handleFocus = () => {
console.log('输入框获得焦点');
};
const handleBlur = () => {
console.log('输入框失去焦点');
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单提交:', value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
<button type="submit">提交</button>
</form>
);
}
UI 事件
TypeScript
function UIEvents() {
const [isVisible, setIsVisible] = useState(true);
return (
<div
onScroll={() => console.log('滚动事件')}
style={{ height: '200px', overflow: 'auto' }}
>
<p>滚动此区域...</p>
<div style={{ height: '1000px' }}>
很长的内容
</div>
{isVisible && (
<div
onTransitionEnd={() => console.log('动画结束')}
onAnimationEnd={() => console.log('动画完成')}
>
动画内容
</div>
)}
</div>
);
}
事件参考
React 支持所有标准浏览器事件。完整列表请参考:
React 事件参考
合成事件 (SyntheticEvent)
什么是合成事件?
React 使用 SyntheticEvent 来包装浏览器原生事件,提供跨浏览器的一致性 API。
TypeScript
function Button() {
const handleClick = (e) => {
console.log('是合成事件:', e instanceof SyntheticEvent);
console.log('原生事件:', e.nativeEvent);
// 阻止默认行为
e.preventDefault();
// 阻止冒泡
e.stopPropagation();
};
return <button onClick={handleClick}>点击</button>;
}
事件池
在 React 17 之前,事件对象会被池化以提升性能。React 17+ 不再使用事件池,所以可以安全地异步访问事件对象:
TypeScript
function Button() {
const handleClick = (e) => {
// ✅ React 17+:可以安全地异步访问
setTimeout(() => {
console.log(e.type);
}, 1000);
};
return <button onClick={handleClick}>点击</button>;
}
访问原生事件
TypeScript
function Button() {
const handleClick = (e) => {
// 访问原生 DOM 事件
const nativeEvent = e.nativeEvent;
console.log('原生事件:', nativeEvent);
// 访问原生 DOM 元素
const domNode = e.target;
console.log('DOM 节点:', domNode);
};
return <button onClick={handleClick}>点击</button>;
}
事件传播
事件冒泡
TypeScript
function EventBubbling() {
const handleParentClick = () => {
console.log('父元素被点击');
};
const handleChildClick = (e) => {
console.log('子元素被点击');
// 不阻止冒泡,事件会继续传播到父元素
};
return (
<div onClick={handleParentClick} style={{ padding: '20px', background: 'lightblue' }}>
<button onClick={handleChildClick}>
点击我(会触发两个处理函数)
</button>
</div>
);
}
停止冒泡
TypeScript
function StopPropagation() {
const handleParentClick = () => {
console.log('父元素被点击');
};
const handleChildClick = (e) => {
e.stopPropagation(); // 阻止事件冒泡
console.log('子元素被点击(只有这个会被执行)');
};
return (
<div onClick={handleParentClick} style={{ padding: '20px', background: 'lightblue' }}>
<button onClick={handleChildClick}>
点击我(只触发子元素处理函数)
</button>
</div>
);
}
阻止默认行为
TypeScript
function PreventDefault() {
const handleClick = (e) => {
e.preventDefault(); // 阻止链接跳转
console.log('链接被点击,但不会跳转');
};
const handleSubmit = (e) => {
e.preventDefault(); // 阻止表单提交
console.log('表单提交被阻止');
};
return (
<div>
<a href="https://example.com" onClick={handleClick}>
点击链接(不会跳转)
</a>
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">提交</button>
</form>
</div>
);
}
注意
preventDefault() 和 stopPropagation() 只影响当前事件。
如果同一元素上有多个处理函数,其他处理函数仍会执行。
受控组件
什么是受控组件?
受控组件是指其值由 React state 控制的表单元素。这是 React 中处理表单的推荐方式。
TypeScript
function Form() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('提交:', { name, email });
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>
姓名:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
</div>
<div>
<label>
邮箱:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
</div>
<button type="submit">提交</button>
</form>
);
}
文本输入
TypeScript
function TextInput() {
const [text, setText] = useState('');
return (
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="输入文本..."
/>
);
}
文本区域
TypeScript
function TextArea() {
const [message, setMessage] = useState('');
return (
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="输入消息..."
rows={5}
/>
);
}
选择框
TypeScript
function SelectBox() {
const [fruit, setFruit] = useState('apple');
return (
<select value={fruit} onChange={(e) => setFruit(e.target.value)}>
<option value="apple">苹果</option>
<option value="banana">香蕉</option>
<option value="orange">橙子</option>
</select>
);
}
复选框
TypeScript
function Checkbox() {
const [isChecked, setIsChecked] = useState(false);
return (
<label>
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
同意条款
</label>
);
}
单选按钮
TypeScript
function RadioButton() {
const [gender, setGender] = useState('male');
return (
<div>
<label>
<input
type="radio"
name="gender"
value="male"
checked={gender === 'male'}
onChange={(e) => setGender(e.target.value)}
/>
男
</label>
<label>
<input
type="radio"
name="gender"
value="female"
checked={gender === 'female'}
onChange={(e) => setGender(e.target.value)}
/>
女
</label>
</div>
);
}
受控 vs 非受控
受控组件:值由 React state 控制(推荐)
非受控组件:值由 DOM 自身管理(使用 ref)
受控组件更容易实现表单验证、条件禁用等功能。
事件监听器
useEffect 添加监听器
TypeScript
import { useEffect, useState } from 'react';
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
window.addEventListener('resize', handleResize);
// 清理:移除监听器
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>窗口宽度:{width}px</div>;
}
键盘快捷键
TypeScript
function KeyboardShortcuts() {
useEffect(() => {
const handleKeyDown = (e) => {
// Ctrl/Cmd + S 保存
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
console.log('保存文档');
}
// Escape 取消
if (e.key === 'Escape') {
console.log('取消操作');
}
};
window.addEventListener('keydown', handleKeyDown);
return () => {
window.removeEventListener('keydown', handleKeyDown);
};
}, []);
return <div>按 Ctrl+S 保存</div>;
}
重要
务必在 useEffect 返回的清理函数中移除事件监听器, 否则会导致内存泄漏。
常见模式
表单验证
TypeScript
function ValidatedForm() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const validateEmail = (email) => {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
};
const handleChange = (e) => {
const value = e.target.value;
setEmail(value);
if (value && !validateEmail(value)) {
setError('请输入有效的邮箱地址');
} else {
setError('');
}
};
const handleSubmit = (e) => {
e.preventDefault();
if (error) {
console.log('表单有错误');
return;
}
console.log('提交:', email);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={handleChange}
/>
{error && <div style={{ color: 'red' }}>{error}</div>}
<button type="submit">提交</button>
</form>
);
}
防抖输入
TypeScript
import { useState, useEffect } from 'react';
function DebouncedInput() {
const [value, setValue] = useState('');
const [debouncedValue, setDebouncedValue] = useState('');
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, 500);
return () => {
clearTimeout(timer);
};
}, [value]);
return (
<div>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="输入文字..."
/>
<p>防抖后的值:{debouncedValue}</p>
</div>
);
}
确认操作
TypeScript
function ConfirmButton() {
const [isConfirmed, setIsConfirmed] = useState(false);
const handleClick = () => {
if (!isConfirmed) {
setIsConfirmed(true);
// 3秒后重置
setTimeout(() => setIsConfirmed(false), 3000);
return;
}
console.log('操作已确认');
setIsConfirmed(false);
};
return (
<button onClick={handleClick}>
{isConfirmed ? '确认删除?' : '删除'}
</button>
);
}
拖拽
TypeScript
function DraggableBox() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const handleMouseDown = (e) => {
setIsDragging(true);
};
useEffect(() => {
const handleMouseMove = (e) => {
if (isDragging) {
setPosition({
x: e.clientX,
y: e.clientY
});
}
};
const handleMouseUp = () => {
setIsDragging(false);
};
if (isDragging) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}
return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging]);
return (
<div
onMouseDown={handleMouseDown}
style={{
position: 'absolute',
left: position.x,
top: position.y,
width: '100px',
height: '100px',
background: 'lightblue',
cursor: isDragging ? 'grabbing' : 'grab'
}}
>
拖拽我
</div>
);
}
最佳实践
1. 使用语义化的事件名称
TypeScript
// ✅ 好:语义化的名称
const handleSubmit = () => { };
const handleClick = () => { };
const handleChange = () => { };
// ❌ 差:不清晰的名称
const doIt = () => { };
const handleEvent = () => { };
2. 提取处理逻辑
TypeScript
// ✅ 好:逻辑分离
const validateForm = () => { };
const submitData = () => { };
const handleSubmit = (e) => {
e.preventDefault();
if (validateForm()) {
submitData();
}
};
// ❌ 差:所有逻辑在一起
const handleSubmit = (e) => {
e.preventDefault();
// 大量逻辑...
};
3. 避免在渲染中创建函数
TypeScript
// ✅ 好:使用 useCallback
const handleClick = useCallback(() => {
doSomething(dependency);
}, [dependency]);
// ❌ 差:每次渲染都创建新函数
<button onClick={() => doSomething(dependency)}>
4. 清理事件监听器
TypeScript
useEffect(() => {
const handleResize = () => { };
window.addEventListener('resize', handleResize);
// ✅ 总是清理
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
5. 使用 TypeScript 类型
TypeScript
const handleClick = (e: MouseEvent<HTMLButtonElement>) => { };
const handleChange = (e: ChangeEvent<HTMLInputElement>) => { };
const handleSubmit = (e: FormEvent<HTMLFormElement>) => { };
常见问题
事件没有触发?
确保你传递的是函数引用,而不是函数调用结果:
TypeScript
// ❌ 错误:立即执行
<button onClick={handleClick()}>
// ✅ 正确:传递函数
<button onClick={handleClick}>
如何获取输入框的值?
TypeScript
const handleChange = (e) => {
const value = e.target.value;
setValue(value);
};
如何阻止表单提交?
TypeScript
const handleSubmit = (e) => {
e.preventDefault();
};
这篇文章有帮助吗?
Previous / Next
Related Links