React中的合成事件绝对不是给当前元素基于addEventListener
单独做的事件绑定,而是基于事件委托处理的!
-
在React17及以后版本,都是委托给
#root
这个容器「捕获和冒泡都做了委托
」; -
在React17以前的版本,都是为委托给
document
容器的「且只做了冒泡阶段
的委托」; -
对于没有实现事件传播机制的事件,才是单独做的事件绑定「例如:onMouseEnter/onMouseLeave…」
在组件渲染的时候,如果发现JSX元素属性中有
onXxx/onXxxCapture
这样的属性,不会给当前元素直接做事件绑定,只是把绑定的方法赋值给元素的相关属性
!
例如:(onClick不是dom事件绑定,onclick才是事件绑定)
outer.onClick=() => {console.log('outer 冒泡「合成」');} //这不是DOM事件绑定「这样的才是 outer.onclick」 outer.onClickCapture=() => {console.log('outer 捕获「合成」');} inner.onClick=() => {console.log('inner 冒泡「合成」');} inner.onClickCapture=() => {console.log('inner 捕获「合成」');}
然后对#root这个容器做了事件绑定「捕获和冒泡都做了」
组件中所渲染的内容,最后都会插入到
#root容器
中,这样点击页面中任何一个元素,最后都会把#root的点击行为触发。而在给#root绑定的方法
中,把之前给元素设置的onXxx/onXxxCapture
属性,在相应的阶段执行!!
React 18版本
React 合成事件事件传播
render() { return <div className="outer" onClick={() => { console.log('outer 冒泡「合成」'); }} onClickCapture={() => { console.log('outer 捕获「合成」'); }}> <div className="inner" onClick={(ev) => { // ev:合成事件对象 console.log('inner 冒泡「合成」', ev, ev.type); }} onClickCapture={() => { console.log('inner 捕获「合成」'); }} ></div> </div>; }
点击inner
元素之后
在componentDidMount周期中,给元素绑定事件,可以看到此时已经“乱序”
componentDidMount() { document.addEventListener('click', () => { console.log('document 捕获'); }, true); document.addEventListener('click', () => { console.log('document 冒泡'); }, false); document.body.addEventListener('click', () => { console.log('body 捕获'); }, true); document.body.addEventListener('click', () => { console.log('body 冒泡'); }, false); let root = document.querySelector('#root'); root.addEventListener('click', () => { console.log('root 捕获'); }, true); root.addEventListener('click', () => { console.log('root 冒泡'); }, false); }
接着我们对inner、outer元素做事件绑定
componentDidMount() { document.addEventListener('click', () => { console.log('document 捕获'); }, true); document.addEventListener('click', () => { console.log('document 冒泡'); }, false); document.body.addEventListener('click', () => { console.log('body 捕获'); }, true); document.body.addEventListener('click', () => { console.log('body 冒泡'); }, false); let root = document.querySelector('#root'); root.addEventListener('click', () => { console.log('root 捕获'); }, true); root.addEventListener('click', () => { console.log('root 冒泡'); }, false); let outer = document.querySelector('.outer'); outer.addEventListener('click', () => { console.log('outer 捕获「原生」'); }, true); outer.addEventListener('click', () => { console.log('outer 冒泡「原生」'); }, false); let inner = document.querySelector('.inner'); inner.addEventListener('click', () => { console.log('inner 捕获「原生」'); }, true); inner.addEventListener('click', (ev) => { // ev:原生事件对象 // ev.stopPropagation(); console.log('inner 冒泡「原生」'); }, false); }
React 合成事件原理
<script> const root = document.querySelector('#root'), outer = document.querySelector('#outer'), inner = document.querySelector('#inner'); // 经过视图渲染解析,outer/inner上都有onXxx/onXxxCapture这样的属性 /* <div className="outer" onClick={() => { console.log('outer 冒泡「合成」'); }} onClickCapture={() => { console.log('outer 捕获「合成」'); }}> <div className="inner" onClick={() => { console.log('inner 冒泡「合成」'); }} onClickCapture={() => { console.log('inner 捕获「合成」'); }} ></div> </div>; */ outer.onClick = () => { console.log('outer 冒泡「合成」'); } outer.onClickCapture = () => { console.log('outer 捕获「合成」'); } inner.onClick = () => { console.log('inner 冒泡「合成」'); } inner.onClickCapture = () => { console.log('inner 捕获「合成」'); } // 给#root做事件绑定 root.addEventListener('click', (ev) => { let path = ev.path; // path:[事件源->....->window] 所有祖先元素 [...path].reverse().forEach(ele => { let handle = ele.onClickCapture; if (handle) handle(); }); }, true); root.addEventListener('click', (ev) => { let path = ev.path; path.forEach(ele => { let handle = ele.onClick; if (handle) handle(); }); }, false); </script>
1、在视图渲染时,遇到合成事件,并没有给元素做事件绑定,而是给元素设置相对应的属性,即合成属性
「 onXxx/onXxxCapture
」
<div className="outer" onClick={() => { console.log('outer 冒泡「合成」'); }} onClickCapture={() => { console.log('outer 捕获「合成」'); }}> <div className="inner" onClick={() => { console.log('inner 冒泡「合成」'); }} onClickCapture={() => { console.log('inner 捕获「合成」'); }} ></div> </div>;
2、给#root
做事件绑定,包括冒泡、捕获,#root
上绑定的方法执行,把所有规划的路径
中,有合成事件属性都执行。
其中ev
是原生事件对象
// 给#root做事件绑定 //捕获阶段 方法A root.addEventListener('click', (ev) => { let path = ev.composedPath(); // path:[事件源->....->window] 所有祖先元素 [...path].reverse().forEach(ele => { let handle = ele.onClickCapture; if (handle) handle(); }); }, true); //冒泡阶段 方法B root.addEventListener('click', (ev) => { let path = ev.composedPath(); path.forEach(ele => { let handle = ele.onClick; if (handle) handle(); }); }, false);
方法A,打印出path
,事件源——>window
方法B,打印出path
,事件源——>window
其中handle
方法,具体的处理逻辑
const handleEv = function(ev){ //ev 原生事件对象 ...... //返回 合成事件对象 } //调用的时候 handleEv(ev)
其中在执行绑定的合成事件handle()时
- 如果不经过处理,方法中的
this
是undefined
- 如果绑定的方法是箭头函数,则找上级上下文中的
this
- 在执行这些方法之前,会把
原生对象ev
做特殊处理,返回合成事件
,传递给函数
点击inner元素的时候,按照原生的事件传播机制
捕获阶段:
window捕获、
document捕获、
html捕获
body捕获
root捕获 ->执行:方法A
– window.onClickCapture (无)
– document.onClickCapture(无)
– html.onClickCapture(无)
– body.onClickCapture(无)
– root.onClickCapture(无)
– outer.onClickCapture => outer 捕获「合成」
– inner.onClickCapture => inner 捕获「合成」
outer 捕获
innner 捕获
冒泡阶段:inner冒泡
outer冒泡
root冒泡->执行:方法B
– inner.onClick = > inner 冒泡「合成」
– outer.onClick = > inner 冒泡「合成」
– body.onClick
– html.onClick
– document.onClick
– window.onClick
可以用图来表示:
React 合成事件中阻止事件传播的影响
<div className="inner" onClick={(ev) => { // ev:合成事件对象 console.log('inner 冒泡「合成」', ev, ev.type); // ev.stopPropagation(); //合成事件对象中的“阻止事件传播”:阻止原生的事件传播 & 阻止合成事件中的事件传播 // ev.nativeEvent.stopPropagation(); //原生事件对象中的“阻止事件传播”:只能阻止原生事件的传播 // ev.nativeEvent.stopImmediatePropagation(); //原生事件对象的阻止事件传播,只不过可以阻止#root上其它绑定的方法执行 /* setTimeout(() => { console.log(ev, ev.type); //React18中并没有事件对象池机制,所以也不存在:创建的事件对象信息清空问题!! }, 500); */ }} onClickCapture={() => { console.log('inner 捕获「合成」'); }}
合成事件stopPropagation
首先 ev.stopPropagation()
,合成事件对象中的“阻止事件传播”:阻止原生的事件传播& 阻止合成事件中的事件传播。
合成事件nativeEvent.stopPropagation
ev.nativeEvent.stopPropagation
:原生事件对象中的“阻止事件传播”:只能阻止原生事件的传播。
合成事件nativeEvent.stopImmediatePropagation
ev.nativeEvent.stopImmediatePropagation :原生事件对象的阻止事件传播,只不过可以阻止#root
上其他绑定的方法执行,此时root冒泡
就没了
原生stopPropagation
inner.addEventListener('click', (ev) => { // ev:原生事件对象 ev.stopPropagation(); console.log('inner 冒泡「原生」'); }, false);
React 16版本
合成事件原理
16版本中,合成事件的处理机制不再把事件委托给root元素,而是委托给document元素
;并且只做了冒泡阶段的委托;在委托的方法中,把onXxx/onXxxCapture合成事件属性执行。
点击inner元素之后,打印结果
1、对视图进行解析
给元素添加合成事件元素,并不是直接做事件绑定
outer.onClick = ()=>{...} outer.onClickCapture = ()=>{...} inner.onClick = ()=>{...} innner.onClickCapture = ()=>{...}
2、对document
的冒泡阶段做了事件委托
document.addEventListener("click",(ev)=>{ //ev:原生事件对象 let path = ev.composedPath();//传播路径:[事件源-> window]; let syntheticEv=处理事件对象(ev) //把捕获阶段的合成事件执行 [...path].reverse().forEach(ele=>{ let handle = ele.onClickCapture; if(handle) handle(syntheticEv) }) //把冒泡阶段的合成事件执行 path.forEach(ele=>{ let handle = ele.onClick; if(handle) handle(syntheticEv) }) })
合成事件中阻止事件传播的影响
<div className="inner" onClick={(ev) => { // ev:合成事件对象 console.log('inner 冒泡「合成」', ev, ev.type); // ev.stopPropagation(); //合成事件对象中的“阻止事件传播”:阻止原生的事件传播 & 阻止合成事件中的事件传播 // ev.nativeEvent.stopPropagation(); //原生事件对象中的“阻止事件传播”:只能阻止原生事件的传播 // ev.nativeEvent.stopImmediatePropagation(); //原生事件对象的阻止事件传播,只不过可以阻止#root上其它绑定的方法执行 /* setTimeout(() => { console.log(ev, ev.type); //React18中并没有事件对象池机制,所以也不存在:创建的事件对象信息清空问题!! }, 500); */ }} onClickCapture={() => { console.log('inner 捕获「合成」'); }} </div>
合成事件stopPropagation()
合成事件nativeEvent.stopPropagation()
合成事件nativeEvent.stopImmediatePropagation()
原生事件stopPropagation
inner.addEventListener('click', (ev) => { // ev:原生事件对象 ev.stopPropagation(); console.log('inner 冒泡「原生」'); }, false);
事件对象池
React 16中,关于合成事件对象的处理,React内部是基于“事件对象池”,做了一个缓存机制。
React 17及以后,是去掉了这套事件对象池和缓存机制
- 当每一次事件触发的时候,如果传播到了委托的元素上「
document/#root
」,在委托的方法中,首先会对内置事件对象做统一处理,生成合成事件对象
React 16 版本中:
为了防止每一次都是重新创建出新的合成事件对象,它设置了一个事件对象池
「缓存池」
- 本次事件触发,获取到事件操作的相关信息后,从 事件对象池 中获取存储的合成事件对象,把信息赋值给相关的成员
- 等待本次操作结束,把合成事件对象中的成员信息都清空掉,再放入到「事件对象池」中
<div className="inner" onClick={(ev) => { // ev:合成事件对象 console.log('inner 冒泡「合成」', ev, ev.type); setTimeout(() => { console.log(ev, ev.type); //React18中并没有事件对象池机制,所以也不存在:创建的事件对象信息清空问题!! }, 500); }} onClickCapture={() => { console.log('inner 捕获「合成」', ev, ev.type); }} ></div>
500ms之后,合成事件对象还在,但是里面的成员信息都被清空了
在React 18 中,并没有事件对象池机制,所以不存在创建的事件对象信息清空的问题。但是React 合成事件中 ev.persist()
可以把合成事件对象中的信息保留下来。
到此这篇关于React合成事件原理及实现(React18和React16)的文章就介绍到这了,更多相关React合成事件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
来源链接:https://www.jb51.net/javascript/336623aat.htm
如有侵犯您的版权,请及时联系3500663466#qq.com(#换@),我们将第一时间删除本站数据。
暂无评论内容