React合成事件原理及實現(React18和React16)
React中的合成事件絕對不是給當前元素基于addEventListener
單獨做的事件綁定,而是基于事件委托處理的!
在React17及以后版本,都是委托給
#root
這個容器「捕獲和冒泡都做了委托
」;在React17以前的版本,都是為委托給
document
容器的「且只做了冒泡階段
的委托」;對于沒有實現事件傳播機制的事件,才是單獨做的事件綁定「例如:onMouseEnter/onMouseLeave…」
在組件渲染的時候,如果發(fā)現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的點擊行為觸發(fā)。而在給#root綁定的方法
中,把之前給元素設置的onXxx/onXxxCapture
屬性,在相應的階段執(zhí)行!!
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
上綁定的方法執(zhí)行,把所有規(guī)劃的路徑
中,有合成事件屬性都執(zhí)行。
其中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)
其中在執(zhí)行綁定的合成事件handle()時
- 如果不經過處理,方法中的
this
是undefined
- 如果綁定的方法是箭頭函數,則找上級上下文中的
this
- 在執(zhí)行這些方法之前,會把
原生對象ev
做特殊處理,返回合成事件
,傳遞給函數
點擊inner元素的時候,按照原生的事件傳播機制
捕獲階段:
window捕獲、
document捕獲、
html捕獲
body捕獲
root捕獲 ->執(zhí)行:方法A
- window.onClickCapture (無)
- document.onClickCapture(無)
- html.onClickCapture(無)
- body.onClickCapture(無)
- root.onClickCapture(無)
- outer.onClickCapture => outer 捕獲「合成」
- inner.onClickCapture => inner 捕獲「合成」
outer 捕獲
innner 捕獲
冒泡階段:inner冒泡
outer冒泡
root冒泡->執(zhí)行:方法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上其它綁定的方法執(zhí)行 /* setTimeout(() => { console.log(ev, ev.type); //React18中并沒有事件對象池機制,所以也不存在:創(chuàng)建的事件對象信息清空問題!! }, 500); */ }} onClickCapture={() => { console.log('inner 捕獲「合成」'); }}
合成事件stopPropagation
首先 ev.stopPropagation()
,合成事件對象中的“阻止事件傳播”:阻止原生的事件傳播& 阻止合成事件中的事件傳播。
合成事件nativeEvent.stopPropagation
ev.nativeEvent.stopPropagation
:原生事件對象中的“阻止事件傳播”:只能阻止原生事件的傳播。
合成事件nativeEvent.stopImmediatePropagation
ev.nativeEvent.stopImmediatePropagation :原生事件對象的阻止事件傳播,只不過可以阻止#root
上其他綁定的方法執(zhí)行,此時root冒泡
就沒了
原生stopPropagation
inner.addEventListener('click', (ev) => { // ev:原生事件對象 ev.stopPropagation(); console.log('inner 冒泡「原生」'); }, false);
React 16版本
合成事件原理
16版本中,合成事件的處理機制不再把事件委托給root元素,而是委托給document元素
;并且只做了冒泡階段的委托;在委托的方法中,把onXxx/onXxxCapture合成事件屬性執(zhí)行。
點擊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) //把捕獲階段的合成事件執(zhí)行 [...path].reverse().forEach(ele=>{ let handle = ele.onClickCapture; if(handle) handle(syntheticEv) }) //把冒泡階段的合成事件執(zhí)行 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上其它綁定的方法執(zhí)行 /* setTimeout(() => { console.log(ev, ev.type); //React18中并沒有事件對象池機制,所以也不存在:創(chuàng)建的事件對象信息清空問題?。? }, 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及以后,是去掉了這套事件對象池和緩存機制
- 當每一次事件觸發(fā)的時候,如果傳播到了委托的元素上「
document/#root
」,在委托的方法中,首先會對內置事件對象做統(tǒng)一處理,生成合成事件對象
React 16 版本中:
為了防止每一次都是重新創(chuàng)建出新的合成事件對象,它設置了一個事件對象池
「緩存池」
- 本次事件觸發(fā),獲取到事件操作的相關信息后,從 事件對象池 中獲取存儲的合成事件對象,把信息賦值給相關的成員
- 等待本次操作結束,把合成事件對象中的成員信息都清空掉,再放入到「事件對象池」中
<div className="inner" onClick={(ev) => { // ev:合成事件對象 console.log('inner 冒泡「合成」', ev, ev.type); setTimeout(() => { console.log(ev, ev.type); //React18中并沒有事件對象池機制,所以也不存在:創(chuàng)建的事件對象信息清空問題!! }, 500); }} onClickCapture={() => { console.log('inner 捕獲「合成」', ev, ev.type); }} ></div>
500ms之后,合成事件對象還在,但是里面的成員信息都被清空了
在React 18 中,并沒有事件對象池機制,所以不存在創(chuàng)建的事件對象信息清空的問題。但是React 合成事件中 ev.persist()
可以把合成事件對象中的信息保留下來。
到此這篇關于React合成事件原理及實現(React18和React16)的文章就介紹到這了,更多相關React合成事件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
React報錯Element type is invalid解決案例
這篇文章主要為大家介紹了React報錯Element type is invalid解決案例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12React18?useState何時執(zhí)行更新及微任務理解
這篇文章主要為大家介紹了React18?useState何時執(zhí)行更新及微任務理解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11Remix 后臺桌面開發(fā)electron-remix-antd-admin
這篇文章主要為大家介紹了Remix 后臺桌面開發(fā)electron-remix-antd-admin的過程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04