本文已参与「新人创作礼」活动,一起开启掘金创作之路。详情
React合成事件
React中有自己的事件系统模式,通常称为React合成事件。跟原生事件使用上有差别。
在传统的HTML中,我们使用事件处理的写法:
<button onclick="activateLasers()">
Activate Lasers
</button>
但是在React中稍有不同:
<button onClick={activateLasers}>
Activate Lasers
</button>
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
此外在React中使用事件还有几点注意:
-
在原生事件中我们可以直接
return false来阻止默认行为。但是在React中不行,需要使用preventDefaultfunction handle(e) { e.preventDefault(); } <a onclick={handle}></a> -
在class组件中使用事件要注意this指向
class Demo extends React.Component { // 方式1:定义事件为回调函数;在元素上直接赋值 // 该方法可以取到合成事件e,但是无法传递其他参数 handle1 = (e) => { console.log(this, e) } // 方式2:定义事件为普通函数,在元素上使用bind // 该方法可传参,合成事件e是在最后一个 handle2(params1, params2, e) { console.log(this) console.log(e, params1, params2) } // 方式3:定义事件为普通函数,在元素上使用回调函数 // 该方法可以传参,可自行定义参数顺序。 handle3(e, params) { console.log(this) console.log(e, params) } render() { return (<div> <button onClick={this.handle1}>btn1</button> <button onClick={this.handle2.bind(this,'参数1', '参数2')}>btn2</button> <button onClick={(e) => this.handle3(e, '参数3')}>btn3</button> </div>) } }
React支持的合成事件:查看
React使用合成事件机制的目的:
- 为了解决跨浏览器的兼容问题,抹平不同浏览器平台的细节差异。
- 为了统一管理事件,提高性能。主要体现在React内部实现事件委托机制上,并记录当前事件的发生状态
React是怎么实现合成事件机制的?
React合成事件原理
事件委托,也就是事件代理机制,这种机制①不会把事件处理函数绑定到真是的DOM节点上,②而是把所有事件绑定在结构最外层,使用一个统一的事件监听和处理函数。
这样做的好处在于统一管理事件。当组件挂载或者卸载时,我们只需要在这个统一管理的地方去增加或者删除一些对象。当元素事件触发时,首先去调用这个统一事件监听器,然后在映射表里找到对应的真正事件去调用真正的事件处理函数。
这样做简化了事件处理和回收机制,提高了React的效率。
另外记录事件状态,即记录事件执行的上下文,这样使得React处理事件时可以判断优先级,优先执行高优先级任务。实现了React的增量渲染的思想,可以预防掉帧使得页面更顺滑。
React将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外有,
React并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理。这样React在更新DOM的时候就不需要考虑如何去处理附着在DOM上的事件监听器,最终达到优化性能的目的
- 所有的事件挂在document上,DOM 事件触发后冒泡到 document;React 找到对应的组件,造出一个合成事件出来;并按组件树模拟一遍事件冒泡。
- event不是原生的,是SyntheticEvent合成事件对象
- 和Vue事件不同,和DOM事件也不同
| React17之前的事件冒泡流程: | React17之后的事件冒泡: |
|---|---|
| 事件冒泡跳过了react的根节点,直接委托在document上。这就使得一个页面中只能有一个版本React,如果有多个版本,事件就乱套了。 | 事件委托不再挂在 document 上,而是挂在 DOM 容器上,也就是 ReactDom.Render 所调用的节点上 |
| React源码展示的处理原生事件列表: |
// react/packages/react-dom/src/__tests__/__snapshots__/ReactTestUtils-test.js.snap
Array [
"abort",
"animationEnd",
"animationIteration",
"animationStart",
"auxClick",
"beforeInput",
"blur",
"canPlay",
"canPlayThrough",
"cancel",
"change",
"click",
"close",
"compositionEnd",
"compositionStart",
"compositionUpdate",
"contextMenu",
"copy",
"cut",
"doubleClick",
"drag",
"dragEnd",
"dragEnter",
"dragExit",
"dragLeave",
"dragOver",
"dragStart",
"drop",
"durationChange",
"emptied",
"encrypted",
"ended",
"error",
"focus",
"gotPointerCapture",
"input",
"invalid",
"keyDown",
"keyPress",
"keyUp",
"load",
"loadStart",
"loadedData",
"loadedMetadata",
"lostPointerCapture",
"mouseDown",
"mouseEnter",
"mouseLeave",
"mouseMove",
"mouseOut",
"mouseOver",
"mouseUp",
"paste",
"pause",
"play",
"playing",
"pointerCancel",
"pointerDown",
"pointerEnter",
"pointerLeave",
"pointerMove",
"pointerOut",
"pointerOver",
"pointerUp",
"progress",
"rateChange",
"reset",
"scroll",
"seeked",
"seeking",
"select",
"stalled",
"submit",
"suspend",
"timeUpdate",
"toggle",
"touchCancel",
"touchEnd",
"touchMove",
"touchStart",
"transitionEnd",
"volumeChange",
"waiting",
"wheel",
]
总结:为何要合成事件
- 兼容性和跨平台
- 挂在统一的document上,减少内存消耗,避免频繁解绑
- 方便事件的统一管理(事务机制)
- dispatchEvent事件机制