Appearance
事件绑定基础
学习目标
通过本章学习,你将掌握:
- React事件处理的基本概念
- 事件绑定的多种方式
- 事件处理器的命名规范
- this绑定的各种方法
- 事件处理的最佳实践
- React 19中的事件处理新特性
- 常见问题与解决方案
第一部分:React事件基础
1.1 什么是React事件
React事件是对原生DOM事件的跨浏览器包装,提供了一致的API和更好的性能。
基本语法
jsx
// HTML原生事件(小写)
<button onclick="handleClick()">Click</button>
// React事件(驼峰命名)
<button onClick={handleClick}>Click</button>
// 主要区别:
// 1. 事件名使用驼峰命名法(onClick而非onclick)
// 2. 事件处理器是函数引用,不是字符串
// 3. 不能通过返回false阻止默认行为简单示例
jsx
function Button() {
// 定义事件处理器
const handleClick = () => {
console.log('按钮被点击了');
};
return (
<button onClick={handleClick}>
点击我
</button>
);
}
// 函数组件的完整示例
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
const decrement = () => {
setCount(count - 1);
};
const reset = () => {
setCount(0);
};
return (
<div>
<p>计数:{count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
<button onClick={reset}>重置</button>
</div>
);
}1.2 常用事件类型
鼠标事件
jsx
function MouseEvents() {
const handleClick = () => console.log('click');
const handleDoubleClick = () => console.log('double click');
const handleMouseDown = () => console.log('mouse down');
const handleMouseUp = () => console.log('mouse up');
const handleMouseEnter = () => console.log('mouse enter');
const handleMouseLeave = () => console.log('mouse leave');
const handleMouseMove = () => console.log('mouse move');
const handleMouseOver = () => console.log('mouse over');
const handleMouseOut = () => console.log('mouse out');
const handleContextMenu = (e) => {
e.preventDefault();
console.log('context menu');
};
return (
<div
onClick={handleClick}
onDoubleClick={handleDoubleClick}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onMouseMove={handleMouseMove}
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
onContextMenu={handleContextMenu}
>
鼠标事件测试区域
</div>
);
}键盘事件
jsx
function KeyboardEvents() {
const handleKeyDown = (e) => {
console.log('Key down:', e.key);
};
const handleKeyUp = (e) => {
console.log('Key up:', e.key);
};
const handleKeyPress = (e) => {
console.log('Key press:', e.key);
};
return (
<input
type="text"
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
onKeyPress={handleKeyPress}
placeholder="输入测试"
/>
);
}
// 实际应用:回车提交
function SearchBox() {
const [query, setQuery] = useState('');
const handleKeyDown = (e) => {
if (e.key === 'Enter') {
handleSearch();
}
};
const handleSearch = () => {
console.log('搜索:', query);
};
return (
<div>
<input
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="按回车搜索"
/>
<button onClick={handleSearch}>搜索</button>
</div>
);
}表单事件
jsx
function FormEvents() {
const handleSubmit = (e) => {
e.preventDefault();
console.log('表单提交');
};
const handleChange = (e) => {
console.log('值改变:', e.target.value);
};
const handleFocus = () => {
console.log('获得焦点');
};
const handleBlur = () => {
console.log('失去焦点');
};
const handleInput = (e) => {
console.log('输入:', e.target.value);
};
const handleReset = () => {
console.log('表单重置');
};
return (
<form onSubmit={handleSubmit} onReset={handleReset}>
<input
type="text"
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
onInput={handleInput}
/>
<button type="submit">提交</button>
<button type="reset">重置</button>
</form>
);
}其他常用事件
jsx
function OtherEvents() {
// 滚动事件
const handleScroll = () => {
console.log('滚动');
};
// 拖拽事件
const handleDragStart = () => console.log('开始拖拽');
const handleDrag = () => console.log('拖拽中');
const handleDragEnd = () => console.log('拖拽结束');
const handleDrop = (e) => {
e.preventDefault();
console.log('放下');
};
const handleDragOver = (e) => {
e.preventDefault();
};
// 焦点事件
const handleFocus = () => console.log('焦点进入');
const handleBlur = () => console.log('焦点离开');
// 剪贴板事件
const handleCopy = () => console.log('复制');
const handleCut = () => console.log('剪切');
const handlePaste = () => console.log('粘贴');
return (
<div>
<div
onScroll={handleScroll}
style={{ height: 100, overflow: 'auto' }}
>
<div style={{ height: 200 }}>滚动区域</div>
</div>
<div
draggable
onDragStart={handleDragStart}
onDrag={handleDrag}
onDragEnd={handleDragEnd}
>
可拖拽元素
</div>
<div
onDrop={handleDrop}
onDragOver={handleDragOver}
>
放置区域
</div>
<input
onFocus={handleFocus}
onBlur={handleBlur}
onCopy={handleCopy}
onCut={handleCut}
onPaste={handlePaste}
/>
</div>
);
}第二部分:事件绑定方式
2.1 函数组件中的事件绑定
方式1:直接定义函数
jsx
function DirectFunction() {
const handleClick = () => {
console.log('点击');
};
return <button onClick={handleClick}>点击</button>;
}
// 带参数的函数
function WithParams() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={handleClick}>增加</button>
</div>
);
}方式2:内联箭头函数
jsx
function InlineArrow() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
{/* 简单逻辑可以内联 */}
<button onClick={() => setCount(count + 1)}>增加</button>
{/* 复杂逻辑不推荐内联 */}
<button onClick={() => {
console.log('当前值:', count);
setCount(count + 1);
console.log('新值:', count + 1);
}}>
增加并打印
</button>
</div>
);
}
// 注意:内联函数的性能影响
function PerformanceIssue() {
const [count, setCount] = useState(0);
return (
<div>
{/* 每次渲染都创建新函数 */}
<ExpensiveChild onClick={() => setCount(count + 1)} />
{/* 如果ExpensiveChild使用React.memo,会失效 */}
</div>
);
}方式3:使用useCallback
jsx
import { useState, useCallback } from 'react';
function UseCallbackExample() {
const [count, setCount] = useState(0);
// 缓存事件处理器
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖,函数引用永不变
return (
<div>
<p>{count}</p>
<ExpensiveChild onClick={handleClick} />
</div>
);
}
// 带依赖的useCallback
function WithDependency({ initialValue }) {
const [count, setCount] = useState(0);
const [multiplier, setMultiplier] = useState(initialValue);
const handleClick = useCallback(() => {
setCount(c => c + multiplier);
}, [multiplier]); // multiplier变化时重新创建
return (
<div>
<p>{count}</p>
<button onClick={handleClick}>增加 {multiplier}</button>
<button onClick={() => setMultiplier(m => m * 2)}>
翻倍倍数
</button>
</div>
);
}2.2 类组件中的事件绑定
方式1:构造函数中绑定
jsx
class BindInConstructor extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
// 在构造函数中绑定this
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
</div>
);
}
}方式2:类字段箭头函数(推荐)
jsx
class ArrowInClass extends React.Component {
state = { count: 0 };
// 类字段 + 箭头函数(自动绑定this)
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
</div>
);
}
}方式3:render中绑定(不推荐)
jsx
class BindInRender extends React.Component {
state = { count: 0 };
handleClick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return (
<div>
<p>{this.state.count}</p>
{/* 方法1:bind(每次渲染创建新函数) */}
<button onClick={this.handleClick.bind(this)}>
Bind
</button>
{/* 方法2:箭头函数(每次渲染创建新函数) */}
<button onClick={() => this.handleClick()}>
Arrow
</button>
</div>
);
}
}
// 性能问题
const MemoChild = React.memo(({ onClick }) => {
console.log('子组件渲染');
return <button onClick={onClick}>子按钮</button>;
});
class Parent extends React.Component {
handleClick() {
console.log('点击');
}
render() {
return (
<div>
{/* 每次都创建新函数,导致MemoChild重新渲染 */}
<MemoChild onClick={() => this.handleClick()} />
</div>
);
}
}2.3 this绑定问题详解
为什么需要绑定this
jsx
class ThisProblem extends React.Component {
state = { message: 'Hello' };
handleClick() {
// 错误:this是undefined
console.log(this.state.message);
// TypeError: Cannot read property 'state' of undefined
}
render() {
return <button onClick={this.handleClick}>点击</button>;
}
}
// 原因:
// JavaScript中,类方法不会自动绑定this
// 事件处理器作为回调传递时,丢失了this上下文
// 模拟过程:
const obj = {
name: 'Alice',
greet() {
console.log(this.name);
}
};
obj.greet(); // 'Alice'(this指向obj)
const greet = obj.greet;
greet(); // undefined(this丢失)所有绑定方案对比
jsx
class AllBindingMethods extends React.Component {
state = { count: 0 };
// 方案1:构造函数绑定
constructor(props) {
super(props);
this.method1 = this.method1.bind(this);
}
method1() {
console.log('方案1', this.state.count);
}
// 方案2:类字段箭头函数(推荐)
method2 = () => {
console.log('方案2', this.state.count);
};
// 方案3:普通方法
method3() {
console.log('方案3', this.state.count);
}
render() {
return (
<div>
<button onClick={this.method1}>方案1</button>
<button onClick={this.method2}>方案2</button>
{/* 方案3的使用方式 */}
<button onClick={this.method3.bind(this)}>
方案3a(不推荐)
</button>
<button onClick={() => this.method3()}>
方案3b(不推荐)
</button>
</div>
);
}
}
// 对比总结:
/*
方案1(构造函数绑定):
- 优点:性能好,只绑定一次
- 缺点:代码冗长,需要手动绑定
方案2(类字段箭头函数):
- 优点:语法简洁,自动绑定
- 缺点:每个实例都有自己的函数副本
方案3a/3b(render中绑定):
- 优点:无
- 缺点:性能差,每次渲染都创建新函数
推荐:方案2(类字段箭头函数)
*/第三部分:事件处理器命名规范
3.1 标准命名约定
jsx
function NamingConvention() {
// 事件处理器命名:handle + 事件名
const handleClick = () => {};
const handleChange = () => {};
const handleSubmit = () => {};
const handleMouseEnter = () => {};
const handleKeyDown = () => {};
// 回调props命名:on + 事件名
const onClick = () => {};
const onChange = () => {};
const onSubmit = () => {};
// 具体业务命名
const handleLoginClick = () => {};
const handleUserNameChange = () => {};
const handleFormSubmit = () => {};
const handleDeleteButtonClick = () => {};
return (
<div>
<button onClick={handleClick}>通用点击</button>
<button onClick={handleLoginClick}>登录点击</button>
<input onChange={handleChange} />
<input onChange={handleUserNameChange} />
</div>
);
}3.2 组件间传递事件
jsx
// 父组件
function Parent() {
const handleChildClick = (data) => {
console.log('子组件点击:', data);
};
return (
<Child onButtonClick={handleChildClick} />
);
}
// 子组件
function Child({ onButtonClick }) {
const handleClick = () => {
const data = { id: 1, name: 'Item' };
onButtonClick(data); // 调用父组件传来的函数
};
return <button onClick={handleClick}>点击我</button>;
}
// 完整示例:Todo应用
function TodoApp() {
const [todos, setTodos] = useState([]);
const handleAddTodo = (text) => {
setTodos([...todos, { id: Date.now(), text }]);
};
const handleDeleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};
const handleToggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
));
};
return (
<div>
<TodoInput onAdd={handleAddTodo} />
<TodoList
todos={todos}
onDelete={handleDeleteTodo}
onToggle={handleToggleTodo}
/>
</div>
);
}
function TodoInput({ onAdd }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim()) {
onAdd(text);
setText('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={e => setText(e.target.value)}
/>
<button type="submit">添加</button>
</form>
);
}
function TodoList({ todos, onDelete, onToggle }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={() => onDelete(todo.id)}
onToggle={() => onToggle(todo.id)}
/>
))}
</ul>
);
}
function TodoItem({ todo, onDelete, onToggle }) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={onToggle}
/>
<span>{todo.text}</span>
<button onClick={onDelete}>删除</button>
</li>
);
}第四部分:阻止默认行为和冒泡
4.1 阻止默认行为
jsx
function PreventDefault() {
// 方式1:使用preventDefault
const handleSubmit = (e) => {
e.preventDefault(); // 阻止表单提交
console.log('表单不会提交到服务器');
};
const handleLinkClick = (e) => {
e.preventDefault(); // 阻止链接跳转
console.log('不会跳转');
};
const handleContextMenu = (e) => {
e.preventDefault(); // 阻止右键菜单
console.log('右键菜单被禁用');
};
// React中不能返回false阻止默认行为(不同于HTML)
const handleClickWrong = () => {
return false; // 无效!
};
return (
<div>
<form onSubmit={handleSubmit}>
<button type="submit">提交</button>
</form>
<a href="https://example.com" onClick={handleLinkClick}>
链接(点击不跳转)
</a>
<div onContextMenu={handleContextMenu}>
右键点击这里
</div>
</div>
);
}
// 实际应用:自定义表单提交
function CustomForm() {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const handleSubmit = async (e) => {
e.preventDefault(); // 阻止默认提交
try {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(formData)
});
const data = await response.json();
console.log('登录成功:', data);
} catch (error) {
console.error('登录失败:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={formData.username}
onChange={e => setFormData({
...formData,
username: e.target.value
})}
/>
<input
type="password"
value={formData.password}
onChange={e => setFormData({
...formData,
password: e.target.value
})}
/>
<button type="submit">登录</button>
</form>
);
}4.2 阻止事件冒泡
jsx
function StopPropagation() {
const handleParentClick = () => {
console.log('父元素点击');
};
const handleChildClick = (e) => {
e.stopPropagation(); // 阻止冒泡
console.log('子元素点击');
// 父元素的handleParentClick不会执行
};
return (
<div onClick={handleParentClick}>
<p>父元素(点击会触发)</p>
<button onClick={handleChildClick}>
子元素(点击不会冒泡到父元素)
</button>
</div>
);
}
// 实际应用:模态框
function Modal({ onClose, children }) {
const handleBackdropClick = () => {
onClose(); // 点击背景关闭
};
const handleModalClick = (e) => {
e.stopPropagation(); // 阻止冒泡到背景
};
return (
<div className="backdrop" onClick={handleBackdropClick}>
<div className="modal" onClick={handleModalClick}>
{children}
<button onClick={onClose}>关闭</button>
</div>
</div>
);
}
// 下拉菜单示例
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
const handleClickOutside = () => {
setIsOpen(false);
};
if (isOpen) {
document.addEventListener('click', handleClickOutside);
}
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, [isOpen]);
const handleToggle = (e) => {
e.stopPropagation();
setIsOpen(!isOpen);
};
const handleMenuClick = (e) => {
e.stopPropagation(); // 点击菜单项不关闭
};
return (
<div>
<button onClick={handleToggle}>
菜单
</button>
{isOpen && (
<div className="menu" onClick={handleMenuClick}>
<div>选项1</div>
<div>选项2</div>
<div>选项3</div>
</div>
)}
</div>
);
}4.3 stopPropagation vs stopImmediatePropagation
jsx
function PropagationDifference() {
const handleClick1 = (e) => {
console.log('处理器1');
e.stopPropagation(); // 阻止冒泡,但同元素的其他处理器仍会执行
};
const handleClick2 = () => {
console.log('处理器2'); // 仍会执行
};
const handleClick3 = (e) => {
console.log('处理器3');
e.stopImmediatePropagation(); // 阻止冒泡,且阻止同元素的其他处理器
};
const handleClick4 = () => {
console.log('处理器4'); // 不会执行
};
return (
<div>
<button
onClick={handleClick1}
onClickCapture={handleClick2}
>
stopPropagation(处理器2会执行)
</button>
<button
onClick={handleClick3}
onClickCapture={handleClick4}
>
stopImmediatePropagation(处理器4不执行)
</button>
</div>
);
}第五部分:事件处理的性能优化
5.1 避免内联函数
jsx
// 不好:每次渲染创建新函数
function Bad() {
const [count, setCount] = useState(0);
return (
<div>
<ChildComponent onClick={() => setCount(count + 1)} />
</div>
);
}
// 好:提取为单独函数
function Good() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<ChildComponent onClick={handleClick} />
</div>
);
}
// 更好:使用useCallback
function Better() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<ChildComponent onClick={handleClick} />
</div>
);
}5.2 事件委托
jsx
// 不好:每个项都绑定事件
function BadList({ items }) {
const handleItemClick = (id) => {
console.log('点击:', id);
};
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => handleItemClick(item.id)}>
{item.text}
</li>
))}
</ul>
);
}
// 好:事件委托到父元素
function GoodList({ items }) {
const handleListClick = (e) => {
const id = e.target.dataset.id;
if (id) {
console.log('点击:', id);
}
};
return (
<ul onClick={handleListClick}>
{items.map(item => (
<li key={item.id} data-id={item.id}>
{item.text}
</li>
))}
</ul>
);
}
// React 19:自动事件委托优化
// React会自动将事件委托到root元素5.3 节流和防抖
jsx
import { useState, useCallback } from 'react';
// 节流(throttle):固定时间间隔执行
function useThrottle(fn, delay) {
const timeoutRef = useRef(null);
const lastRunRef = useRef(0);
return useCallback((...args) => {
const now = Date.now();
if (now - lastRunRef.current >= delay) {
fn(...args);
lastRunRef.current = now;
}
}, [fn, delay]);
}
function ThrottleExample() {
const [scrollY, setScrollY] = useState(0);
const handleScroll = useThrottle(() => {
setScrollY(window.scrollY);
}, 100); // 每100ms最多执行一次
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
return <div>滚动位置: {scrollY}</div>;
}
// 防抖(debounce):延迟执行,期间再次触发会重新计时
function useDebounce(fn, delay) {
const timeoutRef = useRef(null);
return useCallback((...args) => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
fn(...args);
}, delay);
}, [fn, delay]);
}
function DebounceExample() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
const performSearch = useDebounce((term) => {
// 实际搜索逻辑
fetch(`/api/search?q=${term}`)
.then(r => r.json())
.then(setResults);
}, 300); // 停止输入300ms后执行
const handleChange = (e) => {
const value = e.target.value;
setSearchTerm(value);
performSearch(value);
};
return (
<div>
<input value={searchTerm} onChange={handleChange} />
<ul>
{results.map(r => <li key={r.id}>{r.name}</li>)}
</ul>
</div>
);
}第六部分:React 19的事件处理新特性
6.1 自动事件委托优化
jsx
// React 19自动优化事件委托
function AutoDelegation() {
const [items] = useState(Array(1000).fill(0).map((_, i) => ({
id: i,
text: `Item ${i}`
})));
const handleClick = (id) => {
console.log('点击:', id);
};
// React 19会自动优化,不会为每个项创建单独的监听器
return (
<ul>
{items.map(item => (
<li key={item.id} onClick={() => handleClick(item.id)}>
{item.text}
</li>
))}
</ul>
);
}6.2 Server Actions中的事件
jsx
// Server Action
'use server';
async function submitForm(formData) {
const data = {
name: formData.get('name'),
email: formData.get('email')
};
await db.users.create(data);
revalidatePath('/users');
return { success: true };
}
// Client Component
'use client';
import { useActionState } from 'react';
function FormWithServerAction() {
const [state, formAction, isPending] = useActionState(submitForm, null);
return (
<form action={formAction}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? '提交中...' : '提交'}
</button>
{state?.success && <p>提交成功!</p>}
</form>
);
}6.3 useTransition与事件
jsx
import { useState, useTransition } from 'react';
function TransitionEvent() {
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const [isPending, startTransition] = useTransition();
const handleChange = (e) => {
const value = e.target.value;
// 高优先级:立即更新输入框
setInput(value);
// 低优先级:延迟更新列表
startTransition(() => {
const newList = generateLargeList(value);
setList(newList);
});
};
return (
<div>
<input value={input} onChange={handleChange} />
{isPending && <div>更新中...</div>}
<ul>
{list.map((item, i) => <li key={i}>{item}</li>)}
</ul>
</div>
);
}第七部分:常见问题与解决
7.1 事件处理器中的异步操作
jsx
function AsyncEventHandler() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleClick = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<div>
<button onClick={handleClick} disabled={loading}>
{loading ? '加载中...' : '获取数据'}
</button>
{error && <p>错误: {error}</p>}
</div>
);
}7.2 事件对象的持久化
jsx
function EventPersistence() {
const handleClick = (e) => {
// React事件对象会被重用
console.log(e.target); // 正常工作
setTimeout(() => {
console.log(e.target); // 可能是null
}, 100);
};
// 解决方案1:保存需要的值
const handleClickSafe = (e) => {
const target = e.target;
setTimeout(() => {
console.log(target); // 正常工作
}, 100);
};
// 解决方案2:持久化事件对象(不推荐)
const handleClickPersist = (e) => {
e.persist(); // React 17+已废弃
setTimeout(() => {
console.log(e.target);
}, 100);
};
return <button onClick={handleClickSafe}>点击</button>;
}7.3 表单提交问题
jsx
function FormSubmitIssues() {
const handleSubmit = (e) => {
e.preventDefault();
// 问题:直接访问e.target.elements
const form = e.target;
const username = form.elements.username.value;
const password = form.elements.password.value;
console.log({ username, password });
};
// 更好的方式:使用FormData
const handleSubmitBetter = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
console.log(data);
};
// 最佳:受控组件
const [formData, setFormData] = useState({
username: '',
password: ''
});
const handleSubmitBest = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmitBest}>
<input
name="username"
value={formData.username}
onChange={e => setFormData({
...formData,
username: e.target.value
})}
/>
<input
name="password"
type="password"
value={formData.password}
onChange={e => setFormData({
...formData,
password: e.target.value
})}
/>
<button type="submit">提交</button>
</form>
);
}第八部分:最佳实践总结
8.1 事件绑定清单
jsx
// 1. 函数组件:直接定义或useCallback
const handleClick = useCallback(() => {}, []);
// 2. 类组件:类字段箭头函数
handleClick = () => {};
// 3. 避免内联函数(性能敏感场景)
<Component onClick={handleClick} /> // 好
<Component onClick={() => {}} /> // 不好
// 4. 命名规范
const handleClick = () => {}; // 本地处理器
const onClick = () => {}; // props回调
// 5. 阻止默认行为
e.preventDefault();
// 6. 阻止冒泡
e.stopPropagation();
// 7. 异步处理要考虑清理
useEffect(() => {
const handler = () => {};
window.addEventListener('event', handler);
return () => window.removeEventListener('event', handler);
}, []);8.2 性能优化清单
jsx
// 1. 使用useCallback缓存事件处理器
// 2. 避免在render中绑定this
// 3. 大列表使用事件委托
// 4. 频繁事件使用节流/防抖
// 5. React.memo配合稳定的事件处理器练习题
基础练习
- 创建一个按钮,点击时更新计数
- 实现输入框的实时值显示
- 创建一个表单,阻止默认提交
进阶练习
- 实现事件冒泡和阻止冒泡的示例
- 创建一个模态框,点击背景关闭
- 实现防抖搜索功能
高级练习
- 实现一个复杂的表单验证系统
- 创建一个支持拖拽的列表
- 使用React 19的新特性优化事件处理
通过本章学习,你已经全面掌握了React事件绑定的基础知识。事件处理是React交互的核心,理解这些概念对构建动态应用至关重要。继续学习,深入探索事件系统!