1. 事件对象差异
原生JavaScript事件
// 原生事件对象
document.getElementById('btn').addEventListener('click', function(e) {
console.log(e); // 原生Event对象
console.log(e.target); // 真实DOM元素
e.preventDefault(); // 原生方法
e.stopPropagation(); // 原生方法
});React合成事件
// React合成事件
function Button() {
const handleClick = (e) => {
console.log(e); // SyntheticEvent对象
console.log(e.target); // 真实DOM元素
console.log(e.currentTarget); // 真实DOM元素
console.log(e.nativeEvent); // 可以访问原生事件
e.preventDefault(); // React封装的方法
e.stopPropagation(); // React封装的方法
};
return <button onClick={handleClick}>Click me</button>;
}2. 事件委托机制
原生JavaScript
// 需要给每个元素绑定事件
document.getElementById('btn1').addEventListener('click', handler1);
document.getElementById('btn2').addEventListener('click', handler2);
document.getElementById('btn3').addEventListener('click', handler3);React事件委托
// React在根节点统一管理所有事件
// React 17之前:事件委托到document
// React 17之后:事件委托到根容器
function App() {
return (
<div>
<button onClick={handler1}>Button 1</button>
<button onClick={handler2}>Button 2</button>
<button onClick={handler3}>Button 3</button>
</div>
);
}
// 实际上只在根容器上绑定了一个事件监听器3. 跨浏览器兼容性
原生JavaScript
// 需要处理浏览器兼容性
function addEvent(element, type, handler) {
if (element.addEventListener) {
element.addEventListener(type, handler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + type, handler);
} else {
element['on' + type] = handler;
}
}React合成事件
// React自动处理兼容性
function Button() {
const handleClick = (e) => {
// 在所有浏览器中行为一致
console.log(e.type); // 'click'
console.log(e.bubbles); // true
};
return <button onClick={handleClick}>Click</button>;
}4. 事件池化(React 16及之前)
React 16事件池化
function Button() {
const handleClick = (e) => {
console.log(e.type); // 'click'
setTimeout(() => {
console.log(e.type); // null(事件对象被回收)
// 如果需要异步访问,需要调用persist()
}, 100);
};
const handleClickPersist = (e) => {
e.persist(); // 阻止事件池化
setTimeout(() => {
console.log(e.type); // 'click'(正常访问)
}, 100);
};
return <button onClick={handleClick}>Click</button>;
}React 17+取消事件池化
function Button() {
const handleClick = (e) => {
setTimeout(() => {
console.log(e.type); // 'click'(可以正常访问)
// 不再需要persist()
}, 100);
};
return <button onClick={handleClick}>Click</button>;
}5. 事件绑定方式
原生JavaScript
// 多种绑定方式
// 1. HTML内联
<button onclick="handleClick()">Click</button>
// 2. DOM属性
document.getElementById('btn').onclick = handleClick;
// 3. addEventListener
document.getElementById('btn').addEventListener('click', handleClick);React事件绑定
// React只支持props方式绑定
function Button() {
const handleClick = () => {
console.log('clicked');
};
// 正确的绑定方式
return <button onClick={handleClick}>Click</button>;
// 错误的绑定方式(不推荐)
// return <button onclick="handleClick()">Click</button>;
}6. 事件执行顺序
混合使用时的执行顺序
function App() {
useEffect(() => {
// 原生事件绑定
document.addEventListener('click', () => {
console.log('1. document原生事件');
});
document.getElementById('container').addEventListener('click', () => {
console.log('2. container原生事件');
});
}, []);
const handleReactClick = () => {
console.log('3. React合成事件');
};
return (
<div id="container" onClick={handleReactClick}>
<button>Click me</button>
</div>
);
}
// 点击按钮的执行顺序:
// 1. container原生事件
// 2. React合成事件
// 3. document原生事件7. 阻止事件传播的差异
原生事件阻止传播
document.getElementById('btn').addEventListener('click', (e) => {
e.stopPropagation(); // 阻止原生事件冒泡
});React合成事件阻止传播
function Button() {
const handleClick = (e) => {
e.stopPropagation(); // 只能阻止React合成事件冒泡
// 无法阻止原生事件的传播
// 如果要阻止原生事件,需要:
e.nativeEvent.stopImmediatePropagation();
};
return <button onClick={handleClick}>Click</button>;
}8. 性能差异
原生JavaScript
// 每个元素都有独立的事件监听器
const buttons = document.querySelectorAll('button');
buttons.forEach(btn => {
btn.addEventListener('click', handleClick); // 多个监听器
});React事件委托
// 只在根容器有一个监听器
function ButtonList() {
return (
<div>
{buttons.map(btn => (
<button key={btn.id} onClick={handleClick}>
{btn.text}
</button>
))}
</div>
);
}
// 更少的内存占用,更好的性能9. 访问原生事件
通过nativeEvent访问
function Button() {
const handleClick = (e) => {
// React合成事件
console.log(e.type); // 'click'
console.log(e.bubbles); // true
// 访问原生事件
const nativeEvent = e.nativeEvent;
console.log(nativeEvent); // 原生Event对象
console.log(nativeEvent.timeStamp); // 原生时间戳
};
return <button onClick={handleClick}>Click</button>;
}10. 事件名称差异
命名规范不同
// 原生JavaScript事件名
element.addEventListener('click', handler);
element.addEventListener('mouseenter', handler);
element.addEventListener('keydown', handler);
// React合成事件名(驼峰命名)
<button onClick={handler} />
<div onMouseEnter={handler} />
<input onKeyDown={handler} />总结对比表
| 特性 | 原生JavaScript事件 | React合成事件 |
|---|---|---|
| 事件对象 | 原生Event对象 | SyntheticEvent对象 |
| 浏览器兼容 | 需要手动处理 | 自动处理 |
| 事件绑定 | 直接绑定到DOM元素 | 事件委托到根容器 |
| 内存占用 | 每个元素独立监听器 | 统一管理,内存占用少 |
| 事件池化 | 不存在 | React 16有,17+取消 |
| 命名规范 | 小写 | 驼峰命名 |
| 性能 | 元素多时性能较差 | 事件委托,性能更好 |
面试加分点
- 理解事件委托的优势:减少内存占用,提高性能
- 知道React 17的变化:事件委托从document改为根容器,取消事件池化
- 能解释执行顺序:原生事件 → React合成事件 → document原生事件
- 了解兼容性处理:React自动处理跨浏览器兼容性问题
- 掌握混合使用场景:知道何时需要使用nativeEvent
这些知识点展现了你对React事件系统的深入理解,是高级React开发者必备的知识。