React合成事件原理及實現(xiàn)(React18和React16)
React中的合成事件絕對不是給當前元素基于addEventListener單獨做的事件綁定,而是基于事件委托處理的!
在React17及以后版本,都是委托給
#root這個容器「捕獲和冒泡都做了委托」;在React17以前的版本,都是為委托給
document容器的「且只做了冒泡階段的委托」;對于沒有實現(xiàn)事件傳播機制的事件,才是單獨做的事件綁定「例如:onMouseEnter/onMouseLeave…」
在組件渲染的時候,如果發(fā)現(xiàn)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周期中,給元素綁定事件,可以看到此時已經(jīng)“亂序”
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');
// 經(jīng)過視圖渲染解析,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()時
- 如果不經(jīng)過處理,方法中的
this是undefined - 如果綁定的方法是箭頭函數(shù),則找上級上下文中的
this - 在執(zhí)行這些方法之前,會把
原生對象ev做特殊處理,返回合成事件,傳遞給函數(shù)

點擊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合成事件原理及實現(xiàn)(React18和React16)的文章就介紹到這了,更多相關React合成事件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
新建的React Native就遇到vscode報警解除方法
這篇文章主要為大家介紹了新建的React Native就遇到vscode報警解除方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
使用webpack搭建react開發(fā)環(huán)境的方法
本篇文章主要介紹了使用webpack搭建react開發(fā)環(huán)境的方法,在這篇文章中我們開始利用我們之前所學搭建一個簡易的React開發(fā)環(huán)境,用以鞏固我們之前學習的Webpack知識。一起跟隨小編過來看看吧2018-05-05
react項目升級報錯,babel報錯,.babelrc配置兼容等問題及解決
這篇文章主要介紹了react項目升級報錯,babel報錯,.babelrc配置兼容等問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08
React 中 map 處理異步數(shù)據(jù)不渲染的問題及解決方法
文章講述了在使用React時遇到的異步問題導致頁面卡頓的情況,通過Promise.all等方法解決了異步操作的順序問題,使得頁面能夠正常渲染,感興趣的朋友跟隨小編一起看看吧2026-01-01
React?Native實現(xiàn)Toast輕提示和loading效果
這篇文章主要介紹了React Native實現(xiàn)Toast輕提示和loading效果,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09

