React合成事件原理及實(shí)現(xiàn)(React18和React16)
React中的合成事件絕對(duì)不是給當(dāng)前元素基于addEventListener單獨(dú)做的事件綁定,而是基于事件委托處理的!
在React17及以后版本,都是委托給
#root這個(gè)容器「捕獲和冒泡都做了委托」;在React17以前的版本,都是為委托給
document容器的「且只做了冒泡階段的委托」;對(duì)于沒有實(shí)現(xiàn)事件傳播機(jī)制的事件,才是單獨(dú)做的事件綁定「例如:onMouseEnter/onMouseLeave…」
在組件渲染的時(shí)候,如果發(fā)現(xiàn)JSX元素屬性中有
onXxx/onXxxCapture這樣的屬性,不會(huì)給當(dāng)前元素直接做事件綁定,只是把綁定的方法賦值給元素的相關(guān)屬性!
例如:(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 捕獲「合成」');}
然后對(duì)#root這個(gè)容器做了事件綁定「捕獲和冒泡都做了」
組件中所渲染的內(nèi)容,最后都會(huì)插入到
#root容器中,這樣點(diǎn)擊頁(yè)面中任何一個(gè)元素,最后都會(huì)把#root的點(diǎn)擊行為觸發(fā)。而在給#root綁定的方法中,把之前給元素設(shè)置的onXxx/onXxxCapture屬性,在相應(yīng)的階段執(zhí)行!!
React 18版本
React 合成事件事件傳播
render() {
return <div className="outer"
onClick={() => {
console.log('outer 冒泡「合成」');
}}
onClickCapture={() => {
console.log('outer 捕獲「合成」');
}}>
<div className="inner"
onClick={(ev) => {
// ev:合成事件對(duì)象
console.log('inner 冒泡「合成」', ev, ev.type);
}}
onClickCapture={() => {
console.log('inner 捕獲「合成」');
}}
></div>
</div>;
}
點(diǎn)擊inner元素之后

在componentDidMount周期中,給元素綁定事件,可以看到此時(shí)已經(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);
}

接著我們對(duì)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:原生事件對(duì)象
// 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、在視圖渲染時(shí),遇到合成事件,并沒有給元素做事件綁定,而是給元素設(shè)置相對(duì)應(yīng)的屬性,即合成屬性「 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是原生事件對(duì)象
// 給#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 原生事件對(duì)象
......
//返回 合成事件對(duì)象
}
//調(diào)用的時(shí)候
handleEv(ev)
其中在執(zhí)行綁定的合成事件handle()時(shí)
- 如果不經(jīng)過處理,方法中的
this是undefined - 如果綁定的方法是箭頭函數(shù),則找上級(jí)上下文中的
this - 在執(zhí)行這些方法之前,會(huì)把
原生對(duì)象ev做特殊處理,返回合成事件,傳遞給函數(shù)

點(diǎn)擊inner元素的時(shí)候,按照原生的事件傳播機(jī)制
捕獲階段:
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:合成事件對(duì)象
console.log('inner 冒泡「合成」', ev, ev.type);
// ev.stopPropagation(); //合成事件對(duì)象中的“阻止事件傳播”:阻止原生的事件傳播 & 阻止合成事件中的事件傳播
// ev.nativeEvent.stopPropagation(); //原生事件對(duì)象中的“阻止事件傳播”:只能阻止原生事件的傳播
// ev.nativeEvent.stopImmediatePropagation(); //原生事件對(duì)象的阻止事件傳播,只不過可以阻止#root上其它綁定的方法執(zhí)行
/* setTimeout(() => {
console.log(ev, ev.type); //React18中并沒有事件對(duì)象池機(jī)制,所以也不存在:創(chuàng)建的事件對(duì)象信息清空問題??!
}, 500); */
}}
onClickCapture={() => {
console.log('inner 捕獲「合成」');
}}
合成事件stopPropagation
首先 ev.stopPropagation(),合成事件對(duì)象中的“阻止事件傳播”:阻止原生的事件傳播& 阻止合成事件中的事件傳播。

合成事件nativeEvent.stopPropagation
ev.nativeEvent.stopPropagation:原生事件對(duì)象中的“阻止事件傳播”:只能阻止原生事件的傳播。

合成事件nativeEvent.stopImmediatePropagation
ev.nativeEvent.stopImmediatePropagation :原生事件對(duì)象的阻止事件傳播,只不過可以阻止#root上其他綁定的方法執(zhí)行,此時(shí)root冒泡就沒了

原生stopPropagation
inner.addEventListener('click', (ev) => {
// ev:原生事件對(duì)象
ev.stopPropagation();
console.log('inner 冒泡「原生」');
}, false);

React 16版本
合成事件原理
16版本中,合成事件的處理機(jī)制不再把事件委托給root元素,而是委托給document元素;并且只做了冒泡階段的委托;在委托的方法中,把onXxx/onXxxCapture合成事件屬性執(zhí)行。

點(diǎn)擊inner元素之后,打印結(jié)果

1、對(duì)視圖進(jìn)行解析
給元素添加合成事件元素,并不是直接做事件綁定
outer.onClick = ()=>{...}
outer.onClickCapture = ()=>{...}
inner.onClick = ()=>{...}
innner.onClickCapture = ()=>{...}
2、對(duì)document的冒泡階段做了事件委托
document.addEventListener("click",(ev)=>{
//ev:原生事件對(duì)象
let path = ev.composedPath();//傳播路徑:[事件源-> window];
let syntheticEv=處理事件對(duì)象(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:合成事件對(duì)象
console.log('inner 冒泡「合成」', ev, ev.type);
// ev.stopPropagation(); //合成事件對(duì)象中的“阻止事件傳播”:阻止原生的事件傳播 & 阻止合成事件中的事件傳播
// ev.nativeEvent.stopPropagation(); //原生事件對(duì)象中的“阻止事件傳播”:只能阻止原生事件的傳播
// ev.nativeEvent.stopImmediatePropagation(); //原生事件對(duì)象的阻止事件傳播,只不過可以阻止#root上其它綁定的方法執(zhí)行
/* setTimeout(() => {
console.log(ev, ev.type); //React18中并沒有事件對(duì)象池機(jī)制,所以也不存在:創(chuàng)建的事件對(duì)象信息清空問題??!
}, 500); */
}}
onClickCapture={() => {
console.log('inner 捕獲「合成」');
}}
</div>
合成事件stopPropagation()

合成事件nativeEvent.stopPropagation()

合成事件nativeEvent.stopImmediatePropagation()

原生事件stopPropagation
inner.addEventListener('click', (ev) => {
// ev:原生事件對(duì)象
ev.stopPropagation();
console.log('inner 冒泡「原生」');
}, false);

事件對(duì)象池
React 16中,關(guān)于合成事件對(duì)象的處理,React內(nèi)部是基于“事件對(duì)象池”,做了一個(gè)緩存機(jī)制。
React 17及以后,是去掉了這套事件對(duì)象池和緩存機(jī)制
- 當(dāng)每一次事件觸發(fā)的時(shí)候,如果傳播到了委托的元素上「
document/#root」,在委托的方法中,首先會(huì)對(duì)內(nèi)置事件對(duì)象做統(tǒng)一處理,生成合成事件對(duì)象
React 16 版本中:
為了防止每一次都是重新創(chuàng)建出新的合成事件對(duì)象,它設(shè)置了一個(gè)事件對(duì)象池「緩存池」
- 本次事件觸發(fā),獲取到事件操作的相關(guān)信息后,從 事件對(duì)象池 中獲取存儲(chǔ)的合成事件對(duì)象,把信息賦值給相關(guān)的成員
- 等待本次操作結(jié)束,把合成事件對(duì)象中的成員信息都清空掉,再放入到「事件對(duì)象池」中

<div className="inner"
onClick={(ev) => {
// ev:合成事件對(duì)象
console.log('inner 冒泡「合成」', ev, ev.type);
setTimeout(() => {
console.log(ev, ev.type); //React18中并沒有事件對(duì)象池機(jī)制,所以也不存在:創(chuàng)建的事件對(duì)象信息清空問題?。?
}, 500);
}}
onClickCapture={() => {
console.log('inner 捕獲「合成」', ev, ev.type);
}}
></div>
500ms之后,合成事件對(duì)象還在,但是里面的成員信息都被清空了

在React 18 中,并沒有事件對(duì)象池機(jī)制,所以不存在創(chuàng)建的事件對(duì)象信息清空的問題。但是React 合成事件中 ev.persist() 可以把合成事件對(duì)象中的信息保留下來。
到此這篇關(guān)于React合成事件原理及實(shí)現(xiàn)(React18和React16)的文章就介紹到這了,更多相關(guān)React合成事件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React使用高階組件與Hooks實(shí)現(xiàn)權(quán)限攔截教程詳細(xì)分析
高階組件就是接受一個(gè)組件作為參數(shù)并返回一個(gè)新組件(功能增強(qiáng)的組件)的函數(shù)。這里需要注意高階組件是一個(gè)函數(shù),并不是組件,這一點(diǎn)一定要注意,本文給大家分享React高階組件使用小結(jié),一起看看吧2023-01-01
react?express實(shí)現(xiàn)webssh?demo解析
這篇文章主要為大家介紹了詳解react?express實(shí)現(xiàn)webssh?demo解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
使用react context 實(shí)現(xiàn)vue插槽slot功能
這篇文章主要介紹了使用react context 實(shí)現(xiàn)vue插槽slot功能,文中給大家介紹了vue的slot的實(shí)現(xiàn)方法,需要的朋友可以參考下2019-07-07
React useState 使用從基礎(chǔ)到高級(jí)應(yīng)用示例小結(jié)
React 的 useState 是一個(gè)內(nèi)置的 Hook,它允許我們?cè)诤瘮?shù)組件中添加和管理狀態(tài),本文給大家介紹React useState 使用從基礎(chǔ)到高級(jí)應(yīng)用示例小結(jié),感興趣的朋友一起看看吧2025-09-09
Redis數(shù)據(jù)結(jié)構(gòu)面試高頻問題解析
這篇文章主要為大家介紹了Redis數(shù)據(jù)結(jié)構(gòu)高頻面試問題解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
react實(shí)現(xiàn)動(dòng)態(tài)表單
這篇文章主要為大家詳細(xì)介紹了react實(shí)現(xiàn)動(dòng)態(tài)表單,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
react配置webpack-bundle-analyzer項(xiàng)目?jī)?yōu)化踩坑記錄
這篇文章主要介紹了react配置webpack-bundle-analyzer項(xiàng)目?jī)?yōu)化踩坑記錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-06-06
Reactjs?+?Nodejs?+?Mongodb?實(shí)現(xiàn)文件上傳功能實(shí)例詳解
今天是使用?Reactjs?+?Nodejs?+?Mongodb?實(shí)現(xiàn)文件上傳功能,前端我們使用?Reactjs?+?Axios?來搭建前端上傳文件應(yīng)用,后端我們使用?Node.js?+?Express?+?Multer?+?Mongodb?來搭建后端上傳文件處理應(yīng)用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2022-06-06

