React는 세 가지 주요 목적으로 합성 이벤트를 사용합니다. 1. 브라우저 호환성을 달성하고 더 나은 크로스 플랫폼을 달성하기 위해 React에서 제공하는 합성 이벤트를 사용하여 다양한 브라우저 이벤트 객체 간의 차이를 완화하고 다양한 이벤트를 시뮬레이션 및 합성할 수 있습니다. 플랫폼 이벤트. 2. 가비지 수집을 피하세요. React 이벤트 객체는 해제되지 않지만 배열에 저장됩니다. 이벤트가 트리거되면 빈번한 생성 및 파괴(가비지 수집)를 방지하기 위해 배열에서 팝됩니다. 3. 통합 이벤트 관리 및 거래 메커니즘을 촉진합니다.
이 튜토리얼의 운영 환경: Windows 7 시스템, React18 버전, Dell G3 컴퓨터.
1. 합성 이벤트란 무엇인가요?
React 합성 이벤트(SyntheticEvent)는 React가 네이티브 DOM 이벤트의 모든 기능을 시뮬레이션하는 이벤트 객체, 즉 브라우저 네이티브 이벤트에 대한 크로스 브라우저 래퍼입니다. W3C 사양에 따라 합성 이벤트를 정의하고 모든 브라우저와 호환되며 브라우저 기본 이벤트와 동일한 인터페이스를 갖습니다.
React에서는 모든 이벤트가 합성이며 기본 DOM 이벤트가 아니지만 e.nativeEvent 속성을 통해 DOM 이벤트를 얻을 수 있습니다. 예:
const button = <button>react 按钮</button> const handleClick = (e) => console.log(e.nativeEvent); //原生事件对象
새로운 지식을 배울 때, 이 기술이 왜 나타나는지 알아야 합니다.
그렇다면 React는 왜 합성 이벤트를 사용할까요? 여기에는 세 가지 주요 목적이 있습니다.
브라우저 호환성을 활성화하고 더 나은 크로스 플랫폼을 달성합니다.
React는 버블링 일관성을 보장하고 여러 브라우저에서 실행할 수 있는 최상위 이벤트 프록시 메커니즘을 사용합니다. React에서 제공하는 합성 이벤트는 다양한 브라우저 이벤트 객체 간의 차이를 완화하고 다양한 플랫폼 이벤트의 합성 이벤트를 시뮬레이션하는 데 사용됩니다.
가비지 수집 방지
이벤트 객체는 자주 생성되고 재활용될 수 있으므로 React는 이벤트 풀에서 이벤트 객체를 얻거나 해제하기 위해 이벤트 풀을 도입합니다. 즉, React 이벤트 객체는 해제되지 않고 배열에 저장되며, 이벤트가 트리거되면 잦은 생성 및 소멸(가비지 수집)을 방지하기 위해 배열에서 팝됩니다.
편리한 통합 이벤트 관리 및 트랜잭션 메커니즘
本文不介绍源码啦,对具体实现的源码有兴趣的朋友可以查阅:《React SyntheticEvent》 。 https://github.com/facebook/react/blob/75ab53b9e1de662121e68dabb010655943d28d11/packages/events/SyntheticEvent.js#L62
2. 기본 이벤트 검토
JavaScript 이벤트 모델은 주로 원본 이벤트 모델(DOM0), DOM2 이벤트 모델, IE 이벤트의 3가지 유형으로 나뉩니다. 모델 .
1.DOM0 이벤트 모델
은 원래 이벤트 모델이라고도 합니다. 이 모델에서는 이벤트가 전파되지 않습니다. 즉, 이벤트 흐름에 대한 개념이 없습니다. 이벤트 바인딩 청취 기능은 비교적 간단하며 두 가지 방법이 있습니다.
//HTML代码种直接绑定: <button type='button' id="test" onclick="fun()"/> //通过JS代码指定属性值: var btn = document.getElementById('.test'); btn.onclick = fun; //移除监听函数: btn.onclick = null;
장점: 모든 브라우저를 지원하는 강력한 호환성
단점: 로직과 디스플레이가 구분되지 않고 동일한 이벤트에 대해 하나의 청취 기능만 바인딩될 수 있습니다. 나중에 등록된 동일한 항목 이 이벤트는 이전에 등록된 항목을 덮어씁니다.
2.DOM2 이벤트 모델
W3C에서 제정한 표준 모델로 최신 브라우저(IE6-8을 제외한 브라우저)에서 이 모델을 지원합니다. 이 이벤트 모델에는 이벤트에 대한 세 가지 프로세스가 있습니다:
이벤트 캡처 단계(캡처 단계). 이벤트는 문서에서 대상 요소까지 전파되며 전달 노드가 이벤트 수신 함수에 바인딩되어 있는지 확인하고 바인딩된 경우 실행합니다.
이벤트 처리 단계(대상 단계). 이벤트가 대상 요소에 도달하면 대상 요소의 청취 기능이 트리거됩니다.
이벤트 버블링 단계. 이벤트는 대상 요소에서 문서로 버블링되고 전달 노드가 이벤트 수신 함수에 바인딩되어 있는지 확인하고 바인딩되어 있으면 실행합니다.
//事件绑定监听函数的方式如下: addEventListener(eventType, handler, useCapture) //事件移除监听函数的方式如下: removeEventListener(eventType, handler, useCapture)
3.IE 이벤트 모델
IE 이벤트 모델에는
이벤트 처리 단계(대상 단계)라는 두 가지 프로세스가 있습니다. 이벤트가 대상 요소에 도달하면 대상 요소의 청취 기능이 트리거됩니다.
이벤트 버블링 단계. 이벤트는 대상 요소에서 문서로 버블링되고 전달 노드가 이벤트 수신 함수에 바인딩되어 있는지 확인하고 바인딩되어 있으면 실행합니다.
//事件绑定监听函数的方式如下: attachEvent(eventType, handler) //事件移除监听函数的方式如下: detachEvent(eventType, handler)
4. 이벤트 흐름
위 그림과 같이 소위 이벤트 흐름에는 이벤트 캡처, 대상 단계, 이벤트 버블링의 세 단계가 포함됩니다. 이벤트 캡처는 그림에서 빨간색 화살표로 표시된 창 -> html... -> 에 해당하는 외부에서 내부로 이루어집니다. . 이벤트 버블링은 그림의 target -> ->
이벤트 캡처
요소가 이벤트(예: onclick)를 트리거하면 최상위 개체 문서는 이벤트 스트림을 내보내고 DOM 트리의 노드와 함께 대상 요소 노드로 이동합니다. 이벤트가 실제로 발생하는 지점입니다. 이 프로세스 중에는 이벤트의 해당 청취 기능이 트리거되지 않습니다.
이벤트 대상
대상 요소에 도달한 후 대상 요소의 이벤트에 해당하는 처리 기능을 실행합니다. 바인딩된 청취 기능이 없으면 실행되지 않습니다.
이벤트 버블링
从目标元素开始,往顶层元素传播。途中如果有节点绑定了相应的事件处理函数,这些函数都会被触发一次。如果想阻止事件起泡,可以使用 e.stopPropagation()
或者 e.cancelBubble=true(IE)
来阻止事件的冒泡传播。
事件委托/事件代理
简单理解就是将一个响应事件委托到另一个元素。 当子节点被点击时,click 事件向上冒泡,父节点捕获到事件后,我们判断是否为所需的节点,然后进行处理。其优点在于减少内存消耗和动态绑定事件。
三、React合成事件原理
React合成事件的工作原理大致可以分为两个阶段:
事件绑定
事件触发
在React17之前,React是把事件委托在document上的,React17及以后版本不再把事件委托在document上,而是委托在挂载的容器上了,本文以16.x版本的React为例来探寻React的合成事件。当真实的dom触发事件时,此时构造React合成事件对象,按照冒泡或者捕获的路径去收集真正的事件处理函数,在此过程中会先处理原生事件,然后当冒泡到document对象后,再处理React事件。举个栗子:
import React from 'react'; import './App.less'; class Test extends React.Component { parentRef: React.RefObject<any>; childRef: React.RefObject<any>; constructor(props) { super(props); this.parentRef = React.createRef(); this.childRef = React.createRef(); } componentDidMount() { document.addEventListener( 'click', () => { console.log(`document原生事件捕获`); }, true, ); document.addEventListener('click', () => { console.log(`document原生事件冒泡`); }); this.parentRef.current.addEventListener( 'click', () => { console.log(`父元素原生事件捕获`); }, true, ); this.parentRef.current.addEventListener('click', () => { console.log(`父元素原生事件冒泡`); }); this.childRef.current.addEventListener( 'click', () => { console.log(`子元素原生事件捕获`); }, true, ); this.childRef.current.addEventListener('click', () => { console.log(`子元素原生事件冒泡`); }); } handleParentBubble = () => { console.log(`父元素React事件冒泡`); }; handleChildBubble = () => { console.log(`子元素React事件冒泡`); }; handleParentCapture = () => { console.log(`父元素React事件捕获`); }; handleChileCapture = () => { console.log(`子元素React事件捕获`); }; render() { return ( <p ref={this.parentRef} onClick={this.handleParentBubble} onClickCapture={this.handleParentCapture} > <p ref={this.childRef} onClick={this.handleChildBubble} onClickCapture={this.handleChileCapture} > 事件处理测试 </p> </p> ); } } export default Test;
上面案例打印的结果为:
注:React17中上述案例的执行会有所区别,会先执行所有捕获事件后,再执行所有冒泡事件。
1、事件绑定
通过上述案例,我们知道了React合成事件和原生事件执行的过程,两者其实是通过一个叫事件插件(EventPlugin)的模块产生关联的,每个插件只处理对应的合成事件,比如onClick事件对应SimpleEventPlugin插件,这样React在一开始会把这些插件加载进来,通过插件初始化一些全局对象,比如其中有一个对象是registrationNameDependencies,它定义了合成事件与原生事件的对应关系如下:
{ onClick: ['click'], onClickCapture: ['click'], onChange: ['blur', 'change', 'click', 'focus', 'input', 'keydown', 'keyup', 'selectionchange'], ... }
registrationNameModule对象指定了React事件到对应插件plugin的映射:
{ onClick: SimpleEventPlugin, onClickCapture: SimpleEventPlugin, onChange: ChangeEventPlugin, ... }
plugins对象就是上述插件的列表。在某个节点渲染过程中,合成事件比如onClick是作为它的prop的,如果判断该prop为事件类型,根据合成事件类型找到对应依赖的原生事件注册绑定到顶层document上,dispatchEvent为统一的事件处理函数。
2、事件触发
当任意事件触发都会执行dispatchEvent函数,比如上述事例中,当用户点击Child的p时会遍历这个元素的所有父元素,依次对每一级元素进行事件的收集处理,构造合成事件对象(SyntheticEvent–也就是通常我们说的React中自定义函数的默认参数event,原生的事件对象对应它的一个属性),然后由此形成了一条「链」,这条链会将合成事件依次存入eventQueue中,而后会遍历eventQueue模拟一遍捕获和冒泡阶段,然后通过runEventsInBatch方法依次触发调用每一项的监听事件,在此过程中会根据事件类型判断属于冒泡阶段还是捕获阶段触发,比如onClick是在冒泡阶段触发,onClickCapture是在捕获阶段触发,在事件处理完成后进行释放。SyntheticEvent对象属性如下:
boolean bubbles boolean cancelable DOMEventTarget currentTarget boolean defaultPrevented number eventPhase boolean isTrusted DOMEvent nativeEvent // 原生事件对象 void preventDefault() boolean isDefaultPrevented() void stopPropagation() boolean isPropagationStopped() void persist() DOMEventTarget target number timeStamp string type
dispatchEvent伪代码如下:
dispatchEvent = (event) => { const path = []; // 合成事件链 let current = event.target; // 触发事件源 while (current) { path.push(current); current = current.parentNode; // 逐级往上进行收集 } // 模拟捕获和冒泡阶段 // path = [target, p, body, html, ...] for (let i = path.length - 1; i >= 0; i--) { const targetHandler = path[i].onClickCapture; targetHandler && targetHandler(); } for (let i = 0; i < path.length; i++) { const targetHandler = path[i].onClick; targetHandler && targetHandler(); } };
3、更改事件委托(React v17.0)
自React发布以来, 一直自动进行事件委托。当 document 上触发 DOM 事件时,React 会找出调用的组件,然后 React 事件会在组件中向上 “冒泡”。但实际上,原生事件已经冒泡出了 document 级别,React 在其中安装了事件处理器。
但是,这就是逐步升级的困难所在。
在 React 17 中,React 将不再向 document 附加事件处理器。而会将事件处理器附加到渲染 React 树的根 DOM 容器中:
const rootNode = document.getElementById('root'); ReactDOM.render(<App />, rootNode);
在 React 16 或更早版本中,React 会对大多数事件执行 document.addEventListener()。React 17 将会在底层调用 rootNode.addEventListener()
四、注意
由于事件对象可能会频繁创建和回收在React16.x中,合成事件SyntheticEvent采用了事件池,合成事件会被放进事件池中统一管理,这样能够减少内存开销。React通过合成事件,模拟捕获和冒泡阶段,从而达到不同浏览器兼容的目的。另外,React不建议将原生事件和合成事件一起使用,这样很容易造成使用混乱。
버전 17의 이벤트 위임 변경으로 인해 이제 이전 및 새 React 트리를 중첩하는 것이 더 안전해졌습니다. 이것이 작동하려면 두 버전 모두 17 이상이어야 하므로 React 17 이상으로 업그레이드하는 것이 좋습니다.
【관련 추천: Redis 비디오 튜토리얼】
위 내용은 React가 합성 이벤트를 사용하는 이유는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!