事件处理 #
一、事件基础 #
1.1 Preact 事件特点 #
Preact 使用原生 DOM 事件,而非 React 的合成事件:
| 特点 | Preact | React |
|---|---|---|
| 事件系统 | 原生事件 | 合成事件 |
| 事件对象 | 原生 Event | SyntheticEvent |
| 性能 | 更快 | 略慢 |
| 兼容性 | 标准行为 | 跨浏览器统一 |
1.2 事件命名 #
使用驼峰命名法:
jsx
// Preact 事件命名
<button onClick={handleClick}>Click</button>
<input onInput={handleInput} />
<form onSubmit={handleSubmit} />
<input onKeyDown={handleKeyDown} />
1.3 常用事件 #
| 类型 | 事件 |
|---|---|
| 鼠标 | onClick, onDoubleClick, onMouseEnter, onMouseLeave |
| 键盘 | onKeyDown, onKeyUp, onKeyPress |
| 表单 | onInput, onChange, onSubmit, onFocus, onBlur |
| 触摸 | onTouchStart, onTouchMove, onTouchEnd |
二、基本用法 #
2.1 直接定义 #
jsx
function Button() {
return (
<button onClick={() => console.log('Clicked!')}>
Click me
</button>
);
}
2.2 函数引用 #
jsx
function Button() {
const handleClick = () => {
console.log('Button clicked');
};
return <button onClick={handleClick}>Click me</button>;
}
2.3 事件对象 #
jsx
function Form() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form submitted');
};
const handleInput = (e) => {
console.log('Input value:', e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input onInput={handleInput} />
<button type="submit">Submit</button>
</form>
);
}
三、传递参数 #
3.1 箭头函数 #
jsx
function ItemList({ items }) {
const handleDelete = (id) => {
console.log('Delete:', id);
};
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => handleDelete(item.id)}>
Delete
</button>
</li>
))}
</ul>
);
}
3.2 bind 方法 #
jsx
class ItemList extends Component {
handleDelete = (id, e) => {
console.log('Delete:', id);
console.log('Event:', e);
};
render() {
const { items } = this.props;
return (
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={this.handleDelete.bind(this, item.id)}>
Delete
</button>
</li>
))}
</ul>
);
}
}
3.3 数据属性 #
jsx
function ItemList({ items }) {
const handleClick = (e) => {
const id = e.currentTarget.dataset.id;
console.log('Clicked item:', id);
};
return (
<ul>
{items.map(item => (
<li
key={item.id}
data-id={item.id}
onClick={handleClick}
>
{item.name}
</li>
))}
</ul>
);
}
四、表单事件 #
4.1 受控组件 #
jsx
function ControlledForm() {
const [value, setValue] = useState('');
const handleInput = (e) => {
setValue(e.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Submitted:', value);
};
return (
<form onSubmit={handleSubmit}>
<input
value={value}
onInput={handleInput}
placeholder="Type something"
/>
<button type="submit">Submit</button>
</form>
);
}
4.2 多个输入 #
jsx
function MultiInputForm() {
const [form, setForm] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setForm(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log('Form data:', form);
};
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={form.username}
onInput={handleChange}
placeholder="Username"
/>
<input
name="email"
type="email"
value={form.email}
onInput={handleChange}
placeholder="Email"
/>
<input
name="password"
type="password"
value={form.password}
onInput={handleChange}
placeholder="Password"
/>
<button type="submit">Register</button>
</form>
);
}
4.3 复选框和单选框 #
jsx
function CheckboxExample() {
const [preferences, setPreferences] = useState({
newsletter: false,
notifications: true,
darkMode: false
});
const handleCheckbox = (e) => {
const { name, checked } = e.target;
setPreferences(prev => ({
...prev,
[name]: checked
}));
};
return (
<div>
<label>
<input
type="checkbox"
name="newsletter"
checked={preferences.newsletter}
onChange={handleCheckbox}
/>
Subscribe to newsletter
</label>
<label>
<input
type="checkbox"
name="notifications"
checked={preferences.notifications}
onChange={handleCheckbox}
/>
Enable notifications
</label>
</div>
);
}
function RadioExample() {
const [color, setColor] = useState('red');
return (
<div>
<label>
<input
type="radio"
name="color"
value="red"
checked={color === 'red'}
onChange={(e) => setColor(e.target.value)}
/>
Red
</label>
<label>
<input
type="radio"
name="color"
value="blue"
checked={color === 'blue'}
onChange={(e) => setColor(e.target.value)}
/>
Blue
</label>
</div>
);
}
4.4 选择框 #
jsx
function SelectExample() {
const [selected, setSelected] = useState('option1');
return (
<select
value={selected}
onChange={(e) => setSelected(e.target.value)}
>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
);
}
function MultiSelectExample() {
const [selected, setSelected] = useState(['option1']);
const handleChange = (e) => {
const values = Array.from(
e.target.selectedOptions,
option => option.value
);
setSelected(values);
};
return (
<select multiple value={selected} onChange={handleChange}>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
);
}
五、键盘事件 #
5.1 基本用法 #
jsx
function SearchInput() {
const [query, setQuery] = useState('');
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
console.log('Search:', query);
} else if (e.key === 'Escape') {
setQuery('');
}
};
return (
<input
value={query}
onInput={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Press Enter to search"
/>
);
}
5.2 快捷键 #
jsx
function ShortcutExample() {
const handleKeyDown = (e) => {
// Ctrl/Cmd + S
if ((e.ctrlKey || e.metaKey) && e.key === 's') {
e.preventDefault();
console.log('Save triggered');
}
// Ctrl/Cmd + Enter
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
console.log('Submit triggered');
}
};
return (
<div tabIndex={0} onKeyDown={handleKeyDown}>
<p>Press Ctrl+S to save</p>
<p>Press Ctrl+Enter to submit</p>
</div>
);
}
5.3 键码对照 #
jsx
const keys = {
Enter: 'Enter',
Escape: 'Escape',
Tab: 'Tab',
ArrowUp: 'ArrowUp',
ArrowDown: 'ArrowDown',
ArrowLeft: 'ArrowLeft',
ArrowRight: 'ArrowRight',
Backspace: 'Backspace',
Delete: 'Delete',
Space: ' '
};
function handleKeyDown(e) {
switch (e.key) {
case 'Enter':
// 处理回车
break;
case 'Escape':
// 处理退出
break;
case 'ArrowUp':
// 处理上箭头
break;
}
}
六、鼠标事件 #
6.1 基本用法 #
jsx
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e) => {
setPosition({
x: e.clientX,
y: e.clientY
});
};
return (
<div
style={{ height: '300px', border: '1px solid #ccc' }}
onMouseMove={handleMouseMove}
>
<p>Mouse position: {position.x}, {position.y}</p>
</div>
);
}
6.2 悬停效果 #
jsx
function HoverCard({ children }) {
const [isHovered, setIsHovered] = useState(false);
return (
<div
style={{
padding: '20px',
backgroundColor: isHovered ? '#f0f0f0' : '#fff',
transition: 'background-color 0.2s'
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{children}
</div>
);
}
6.3 拖拽 #
jsx
function Draggable() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [dragging, setDragging] = useState(false);
const [offset, setOffset] = useState({ x: 0, y: 0 });
const handleMouseDown = (e) => {
setDragging(true);
setOffset({
x: e.clientX - position.x,
y: e.clientY - position.y
});
};
const handleMouseMove = (e) => {
if (!dragging) return;
setPosition({
x: e.clientX - offset.x,
y: e.clientY - offset.y
});
};
const handleMouseUp = () => {
setDragging(false);
};
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: 'absolute',
left: position.x,
top: position.y,
cursor: dragging ? 'grabbing' : 'grab',
userSelect: 'none'
}}
onMouseDown={handleMouseDown}
>
Drag me
</div>
);
}
七、事件委托 #
7.1 基本概念 #
jsx
function List({ items }) {
const handleClick = (e) => {
const target = e.target.closest('li');
if (target) {
const id = target.dataset.id;
console.log('Clicked item:', id);
}
};
return (
<ul onClick={handleClick}>
{items.map(item => (
<li key={item.id} data-id={item.id}>
{item.name}
<button>Delete</button>
<button>Edit</button>
</li>
))}
</ul>
);
}
7.2 区分目标 #
jsx
function ActionList({ items }) {
const handleClick = (e) => {
const button = e.target.closest('button');
if (!button) return;
const action = button.dataset.action;
const id = button.closest('li').dataset.id;
switch (action) {
case 'delete':
console.log('Delete:', id);
break;
case 'edit':
console.log('Edit:', id);
break;
case 'view':
console.log('View:', id);
break;
}
};
return (
<ul onClick={handleClick}>
{items.map(item => (
<li key={item.id} data-id={item.id}>
{item.name}
<button data-action="view">View</button>
<button data-action="edit">Edit</button>
<button data-action="delete">Delete</button>
</li>
))}
</ul>
);
}
八、自定义事件 #
8.1 创建自定义事件 #
jsx
function CustomEventExample() {
const dispatchCustomEvent = () => {
const event = new CustomEvent('myCustomEvent', {
detail: { message: 'Hello from custom event' }
});
document.dispatchEvent(event);
};
useEffect(() => {
const handleCustomEvent = (e) => {
console.log('Custom event received:', e.detail);
};
document.addEventListener('myCustomEvent', handleCustomEvent);
return () => {
document.removeEventListener('myCustomEvent', handleCustomEvent);
};
}, []);
return (
<button onClick={dispatchCustomEvent}>
Dispatch Custom Event
</button>
);
}
8.2 组件间通信 #
jsx
const EventBus = {
listeners: {},
on(event, callback) {
if (!this.listeners[event]) {
this.listeners[event] = [];
}
this.listeners[event].push(callback);
},
off(event, callback) {
if (!this.listeners[event]) return;
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
},
emit(event, data) {
if (!this.listeners[event]) return;
this.listeners[event].forEach(callback => callback(data));
}
};
function Publisher() {
const handleClick = () => {
EventBus.emit('message', { text: 'Hello from Publisher' });
};
return <button onClick={handleClick}>Send Message</button>;
}
function Subscriber() {
const [message, setMessage] = useState('');
useEffect(() => {
const handleMessage = (data) => {
setMessage(data.text);
};
EventBus.on('message', handleMessage);
return () => EventBus.off('message', handleMessage);
}, []);
return <p>Received: {message}</p>;
}
九、最佳实践 #
9.1 防抖和节流 #
jsx
// 防抖 Hook
function useDebounce(callback, delay) {
const timeoutRef = useRef(null);
return (...args) => {
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
callback(...args);
}, delay);
};
}
// 使用
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedSearch = useDebounce((value) => {
console.log('Searching:', value);
}, 300);
const handleInput = (e) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return <input value={query} onInput={handleInput} />;
}
9.2 阻止默认行为 #
jsx
function Link({ href, children }) {
const handleClick = (e) => {
e.preventDefault();
console.log('Navigating to:', href);
};
return (
<a href={href} onClick={handleClick}>
{children}
</a>
);
}
9.3 停止冒泡 #
jsx
function Modal({ onClose, children }) {
const handleBackdropClick = () => {
onClose();
};
const handleContentClick = (e) => {
e.stopPropagation();
};
return (
<div class="modal-backdrop" onClick={handleBackdropClick}>
<div class="modal-content" onClick={handleContentClick}>
{children}
</div>
</div>
);
}
十、总结 #
| 要点 | 说明 |
|---|---|
| 命名 | 驼峰命名 onClick |
| 参数 | 箭头函数或 bind |
| 表单 | 受控组件模式 |
| 键盘 | e.key 判断按键 |
| 委托 | 利用事件冒泡 |
| 清理 | 移除事件监听器 |
核心原则:
- 使用受控组件处理表单
- 合理使用事件委托
- 记得清理副作用
- 防抖节流优化性能
最后更新:2026-03-28