用户交互与响应详解
React事件系统:React使用合成事件(SyntheticEvent)系统,它封装了原生浏览器事件,提供了一致的API,并自动处理跨浏览器兼容性问题。
// 传统方式
const button = document.getElementById('btn');
button.onclick = function(event) {
console.log('按钮被点击');
event.preventDefault();
};
// React方式
function Button() {
const handleClick = (event) => {
console.log('按钮被点击');
event.preventDefault();
};
return (
<button onClick={handleClick}>
点击我
</button>
);
}
// 事件绑定的基本语法
function EventExample() {
// 定义事件处理函数
const handleClick = () => {
console.log('按钮被点击了!');
};
const handleMouseOver = () => {
console.log('鼠标悬停');
};
const handleMouseOut = () => {
console.log('鼠标离开');
};
return (
<div>
<button onClick={handleClick}>
点击我
</button>
<div
onMouseOver={handleMouseOver}
onMouseOut={handleMouseOut}
>
鼠标悬停区域
</div>
</div>
);
}
重要警告:事件处理函数应该传递函数引用,而不是调用函数!
// 错误:立即调用函数
function BadExample() {
const handleClick = () => {
console.log('点击');
};
return (
<button onClick={handleClick()}>
点击我
</button>
);
}
// 正确:传递函数引用
function GoodExample() {
const handleClick = () => {
console.log('点击');
};
return (
<button onClick={handleClick}>
点击我
</button>
);
}
事件对象:React事件处理函数会自动接收一个事件对象参数,这个对象包含了事件的相关信息。
function EventObjectExample() {
const handleClick = (event) => {
console.log('事件类型:', event.type);
console.log('目标元素:', event.target);
console.log('当前元素:', event.currentTarget);
console.log('鼠标位置:', event.clientX, event.clientY);
console.log('按键:', event.key);
};
const handleInputChange = (event) => {
console.log('输入值:', event.target.value);
console.log('元素名称:', event.target.name);
};
return (
<div>
<button onClick={handleClick}>
点击查看事件对象
</button>
<input
type="text"
name="username"
onChange={handleInputChange}
placeholder="输入文字"
/>
</div>
);
}
function PreventDefaultExample() {
const handleSubmit = (event) => {
event.preventDefault();
console.log('表单提交被阻止');
};
const handleLinkClick = (event) => {
event.preventDefault();
console.log('链接点击被阻止');
};
return (
<div>
<form onSubmit={handleSubmit}>
<button type="submit">
提交表单(不会刷新页面)
</button>
</form>
<a href="https://example.com" onClick={handleLinkClick}>
点击链接(不会跳转)
</a>
</div>
);
}
function StopPropagationExample() {
const handleParentClick = () => {
console.log('父元素被点击');
};
const handleChildClick = (event) => {
event.stopPropagation();
console.log('子元素被点击(阻止冒泡)');
};
return (
<div
onClick={handleParentClick}
style={{
padding: '20px',
backgroundColor: '#e0e0e0',
cursor: 'pointer'
}}
>
父元素
<div
onClick={handleChildClick}
style={{
padding: '10px',
backgroundColor: '#61DAFB',
marginTop: '10px',
cursor: 'pointer'
}}
>
子元素(点击不会触发父元素)
</div>
</div>
);
}
| 事件类型 | React属性 | 说明 |
|---|---|---|
| 鼠标事件 | onClick, onDoubleClick, onMouseDown, onMouseUp, onMouseMove, onMouseOver, onMouseOut | 鼠标点击、移动等操作 |
| 键盘事件 | onKeyDown, onKeyPress, onKeyUp | 键盘按键操作 |
| 表单事件 | onChange, onSubmit, onInput, onFocus, onBlur | 表单输入和提交 |
| 焦点事件 | onFocus, onBlur | 元素获得或失去焦点 |
| 剪贴板事件 | onCopy, onCut, onPaste | 复制、剪切、粘贴操作 |
| 滚动事件 | onScroll | 元素滚动 |
function PassParametersExample() {
const handleClick = (message, id) => {
console.log('消息:', message);
console.log('ID:', id);
};
const items = [
{ id: 1, name: '项目1' },
{ id: 2, name: '项目2' },
{ id: 3, name: '项目3' }
];
return (
<div>
<button onClick={() => handleClick('Hello', 1)}>
点击我
</button>
<ul>
{items.map(item => (
<li key={item.id}>
{item.name}
<button onClick={() => handleClick('删除', item.id)}>
删除
</button>
</li>
))}
</ul>
</div>
);
}
function BindExample() {
const handleClick = function(id, event) {
console.log('ID:', id);
console.log('事件:', event);
};
return (
<button onClick={handleClick.bind(null, 1)}>
点击我
</button>
);
}
推荐方式:使用箭头函数比bind更简洁易读,是React中传递参数的推荐方式。
function KeyboardEventExample() {
const handleKeyDown = (event) => {
console.log('按下的键:', event.key);
console.log('键码:', event.keyCode);
if (event.key === 'Enter') {
console.log('按下了回车键');
}
if (event.key === 'Escape') {
console.log('按下了ESC键');
}
};
const handleKeyPress = (event) => {
console.log('按键字符:', event.key);
};
const handleKeyUp = (event) => {
console.log('释放的键:', event.key);
};
return (
<div>
<input
type="text"
placeholder="在此输入(按Enter键)"
onKeyDown={handleKeyDown}
onKeyPress={handleKeyPress}
onKeyUp={handleKeyUp}
/>
</div>
);
}
function InputEventExample() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
console.log('当前值:', event.target.value);
};
const handleFocus = () => {
console.log('输入框获得焦点');
};
const handleBlur = () => {
console.log('输入框失去焦点');
};
return (
<div>
<input
type="text"
value={value}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
placeholder="输入文字"
/>
<p>输入的值: {value}</p>
</div>
);
}
function FormSubmitExample() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
});
const handleChange = (event) => {
const { name, value } = event.target;
setFormData({
...formData,
[name]: value
});
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('表单数据:', formData);
// 这里可以发送数据到服务器
// fetch('/api/register', {
// method: 'POST',
// body: JSON.stringify(formData)
// });
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用户名:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</div>
<div>
<label>邮箱:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</div>
<div>
<label>密码:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</div>
<button type="submit">提交</button>
</form>
);
}
// 子组件
function ChildButton({ onClick, children }) {
return (
<button onClick={onClick}>
{children}
</button>
);
}
// 父组件
function ParentComponent() {
const handleClick = () => {
console.log('按钮被点击了!');
};
return (
<div>
<ChildButton onClick={handleClick}>
点击我
</ChildButton>
</div>
);
}
function CustomEventHandler() {
const handleMouseMove = (event) => {
const x = event.clientX;
const y = event.clientY;
console.log(`鼠标位置: (${x}, ${y})`);
};
return (
<div
onMouseMove={handleMouseMove}
style={{
width: '300px',
height: '200px',
backgroundColor: '#f0f0f0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'crosshair'
}}
>
在这里移动鼠标
</div>
);
}
import { useState, useCallback } from 'react';
function BestPracticesExample() {
const [count, setCount] = useState(0);
// 使用useCallback优化性能
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
const handleReset = useCallback(() => {
setCount(0);
}, []);
return (
<div>
<h2>计数: {count}</h2>
<button onClick={handleClick}>增加</button>
<button onClick={handleReset}>重置</button>
</div>
);
}
任务:创建一个交互式计算器
要求:
// 参考答案
function Calculator() {
const [display, setDisplay] = useState('0');
const [history, setHistory] = useState([]);
const [operator, setOperator] = useState(null);
const [previousValue, setPreviousValue] = useState(null);
const [waitingForOperand, setWaitingForOperand] = useState(false);
const inputDigit = (digit) => {
if (waitingForOperand) {
setDisplay(String(digit));
setWaitingForOperand(false);
} else {
setDisplay(display === '0' ? String(digit) : display + digit);
}
};
const inputDot = () => {
if (waitingForOperand) {
setDisplay('0.');
setWaitingForOperand(false);
} else if (display.indexOf('.') === -1) {
setDisplay(display + '.');
}
};
const clear = () => {
setDisplay('0');
setOperator(null);
setPreviousValue(null);
setWaitingForOperand(false);
};
const performOperation = (nextOperator) => {
const inputValue = parseFloat(display);
if (previousValue === null) {
setPreviousValue(inputValue);
} else if (operator) {
const currentValue = previousValue || 0;
const newValue = calculate(currentValue, inputValue, operator);
setDisplay(String(newValue));
setPreviousValue(newValue);
setHistory([...history, `${currentValue} ${operator} ${inputValue} = ${newValue}`]);
}
setWaitingForOperand(true);
setOperator(nextOperator);
};
const calculate = (firstValue, secondValue, operation) => {
switch (operation) {
case '+':
return firstValue + secondValue;
case '-':
return firstValue - secondValue;
case '×':
return firstValue * secondValue;
case '÷':
return secondValue !== 0 ? firstValue / secondValue : 'Error';
default:
return secondValue;
}
};
const handleEqual = () => {
if (!operator || previousValue === null) return;
const inputValue = parseFloat(display);
const result = calculate(previousValue, inputValue, operator);
setDisplay(String(result));
setHistory([...history, `${previousValue} ${operator} ${inputValue} = ${result}`]);
setPreviousValue(null);
setOperator(null);
setWaitingForOperand(true);
};
const handleKeyDown = (event) => {
const key = event.key;
if (/[0-9]/.test(key)) {
inputDigit(parseInt(key));
} else if (key === '.') {
inputDot();
} else if (key === '+') {
performOperation('+');
} else if (key === '-') {
performOperation('-');
} else if (key === '*') {
performOperation('×');
} else if (key === '/') {
event.preventDefault();
performOperation('÷');
} else if (key === 'Enter' || key === '=') {
handleEqual();
} else if (key === 'Escape' || key === 'c' || key === 'C') {
clear();
} else if (key === 'Backspace') {
setDisplay(display.length > 1 ? display.slice(0, -1) : '0');
}
};
return (
<div style={{ maxWidth: '300px', margin: '20px auto' }}>
<div style={{
backgroundColor: '#20232a',
color: '#61DAFB',
padding: '20px',
fontSize: '32px',
textAlign: 'right',
marginBottom: '10px'
}}>
{display}
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '5px' }}>
<button onClick={clear}>C</button>
<button onClick={() => performOperation('÷')}>÷</button>
<button onClick={() => performOperation('×')}>×</button>
<button onClick={() => setDisplay(display.length > 1 ? display.slice(0, -1) : '0')}>←</button>
{[7, 8, 9].map(digit => (
<button key={digit} onClick={() => inputDigit(digit)}>{digit}</button>
))}
<button onClick={() => performOperation('-')}>-</button>
{[4, 5, 6].map(digit => (
<button key={digit} onClick={() => inputDigit(digit)}>{digit}</button>
))}
<button onClick={() => performOperation('+')}>+</button>
{[1, 2, 3].map(digit => (
<button key={digit} onClick={() => inputDigit(digit)}>{digit}</button>
))}
<button
onClick={handleEqual}
style={{ gridRow: 'span 2' }}
>
=
</button>
<button
onClick={() => inputDigit(0)}
style={{ gridColumn: 'span 2' }}
>
0
</button>
<button onClick={inputDot}>.</button>
</div>
<div style={{ marginTop: '20px' }}>
<h3>计算历史</h3>
<ul style={{ maxHeight: '150px', overflowY: 'auto' }}>
{history.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
</div>
);
}