(1)react合成事件
react合成事件執(zhí)行過程:
合成事件與原生事件的區(qū)別
1. 寫法不同沪摄,合適事件是駝峰寫法蜒犯,而原生事件是全部小寫
2. 執(zhí)行時機(jī)不同潘鲫,合適事件全部委托到document上引润,而原生事件綁定到DOM元素本身
3. 合成事件中可以是任何類型,比如this.handleClick這個函數(shù),而原生事件中只能是字符串
// react合成事件
<button onClick={this.handleClick}></button>
// DOM原生事件
<input value={this.state.name} onChange={this.handleChange} />
(2)React組件中使用原生事件
由于原生事件綁定在真實(shí)DOM上勤揩,所以一般是在componentDidMount中或ref回調(diào)函數(shù)中進(jìn)行
注意在componentWillUnmount階段進(jìn)行解綁操作帝火,以避免內(nèi)存泄漏。
class ReactEvent extends Component {
? ? componentDidMount() {
? ? ? ? //獲取當(dāng)前真實(shí)DOM元素
? ? ? ? const thisDOM = ReactDOM.findDOMNode(this);
? ? ? ? //或者
? ? ? ? const thisDOM = this.refs.con;
? ? ? ? thisDOM.addEventListener('click',this.onDOMClick,false);
? ? }
? ? componentWillUnmount() {
? ? ? ? //卸載時解綁事件风罩,防止內(nèi)存泄漏
? ? ? ? const thisDOM = ReactDOM.findDOMNode(this);
? ? ? ? thisDOM.removeEventListener('click',this.removeDOMClick);
? ? }
? ? onDOMClick(e){
? ? ? ? console.log(e)
? ? }
? ? render(){
? ? ? ? return(
? ? ? ? ? ? <div ref="con">
? ? ? ? ? ? ? ? 單擊原始事件觸發(fā)
? ? ? ? ? ? </div>
? ? ? ? )
? ? }
}
}
export default ReactEvent
在componentDidMount周期中糠排,使用addEventListener直接綁定即可,dom元素使用ref或者
ReactDOM.findDOMNode來獲取超升。
合成事件和原生事件可以混合使用入宦,但是盡量避免這種情況出現(xiàn)哺徊,因?yàn)槿菀讓?dǎo)致混亂,則某些情況下
可以使用乾闰。合成事件和DOM事件混合使用落追,觸發(fā)順序是:
1. 先執(zhí)行原生事件,事件冒泡至document涯肩,再執(zhí)行合成事件
2. 如果是父子元素轿钠,觸發(fā)順序?yàn)?子元素原生事件 -> 父元素原生事件 -> 子元素合成事件 -> 父元素合成事件
如下例子:
class ReactEvent extends Component {
? ? constructor(props){
? ? ? ? super(props)
? ? ? ? this.state = {
? ? ? ? ? ? value: ''
? ? ? ? }
? ? ? ? this.onReactClick = this.onReactClick.bind(this)
? ? ? ? this.onReactChildClick = this.onReactChildClick.bind(this)
? ? }
? ? componentDidMount() {
? ? ? ? //獲取當(dāng)前真實(shí)DOM元素
? ? ? ? const thisDOM = ReactDOM.findDOMNode(this);
? ? ? ? thisDOM.addEventListener('click',this.onDOMClick,false);
? ? ? ? //獲取子元素并綁定事件
? ? ? ? const thisDOMChild = thisDOM.querySelector(".child");
? ? ? ? thisDOMChild.addEventListener('click',this.onDOMChildClick,false);
? ? }
? ? onDOMClick(e){
? ? ? ? console.log("父組件原生事件綁定事件觸發(fā)")
? ? }
? ? onReactClick(e){
? ? ? ? console.log("父組件React合成事件綁定事件觸發(fā)")
? ? }
? ? onDOMChildClick(e){
? ? ? ? e.stopPropagation()
? ? ? ? console.log("子元素原生事件綁定事件觸發(fā)")
? ? }
? ? onReactChildClick(e){
? ? ? ? console.log("子元素React合成事件綁定事件觸發(fā)")
? ? }
? ? render(){
? ? ? ? ? ? return(
? ? ? ? ? ? ? ? <div onClick={this.onReactClick}>
? ? ? ? ? ? ? ? ? ? 父元素單擊事件觸發(fā)
? ? ? ? ? ? ? ? ? ? <button className="child" onClick={this.onReactChildClick}>子元素單擊事件觸發(fā)</button>
? ? ? ? ? ? ? ? </div>
? ? ? ? ? ? )
? ? }
}
}
export default ReactEvent
通過設(shè)置原生事件綁定為冒泡階段調(diào)用,且每次測試單擊子元素按鈕:
1.在子元素原生事件程序中阻止事件傳播病苗,則打印出:
子元素原生事件綁定事件觸發(fā)疗垛;
2.在父元素元素事件程序中阻止事件傳播,則打印出:
子元素原生事件綁定事件觸發(fā)
父組件原生事件綁定事件觸發(fā)
3.在子元素React合成事件onClick中阻止事件傳播硫朦,則打印出:
子元素原生事件綁定事件觸發(fā)
父組件原生事件綁定事件觸發(fā)
子元素React合成事件綁定事件觸發(fā)
4.在父元素React合成事件onClick中阻止事件傳播贷腕,則打印出:
子元素原生事件綁定事件觸發(fā)
父組件原生事件綁定事件觸發(fā)
子元素React合成事件綁定事件觸發(fā)
父組件React合成事件綁定事件觸發(fā)
可以看到若不阻止事件傳播每次(單擊子元素)事件觸發(fā)流程是:
Document->子元素(原生事件觸發(fā))->父元素(原生事件)->回到Document->React子元素合成事件監(jiān)聽器觸發(fā) ->React父元素合成事件監(jiān)聽器觸發(fā)
(3)阻止冒泡
基本原則
阻止合成事件的冒泡不會阻止原生事件的冒泡,但是阻止原生事件的冒泡會阻止合成事件的冒泡咬展。
1)阻止合成事件冒泡泽裳,用e.stopPropagation()
例如:
render() {
? ? ? ? return
? ? ? ? ? ? <div onClick={this.outClick}>
? ? ? ? ? ? ? ? <button onClick={this.onClick}> 測試click事件 </button>
? ? ? ? ? ? </div>
}
outClick是外層合成事件,用e.stopPropagation會阻止其運(yùn)行挚赊,但是不能阻止原生事件诡壁,例如document上用addEventListener綁定的事件
2) 阻止原生事件和合成事件冒泡(因?yàn)樽柚沽嗽录蜁柚购铣墒录?,用e.nativeEvent.stopImmediatePropagation();
3) 在document或body上注冊的原生事件方法荠割,可以通過e.target判斷來阻止冒泡事件的執(zhí)行
class App extends React.Component {
? constructor(props) {
? ? super(props);
? ? this.state = {
? ? ? showCalender: false
? ? };
? }
? componentDidMount() {
? ? document.addEventListener('click', (e) => {
? ? ? var tar = document.getElementById('myInput');
? ? ? //判斷e.target使得document事件不往下執(zhí)行
? ? ? if (tar.contains(e.target)) return;
? ? ? console.log('document!!!');
? ? ? this.setState({showCalender: false});
? ? }, false);
? }
? render() {
? ? return (<div>
? ? ? <input
? ? ? ? id="myInput"
? ? ? ? type="text"
? ? ? ? onClick={(e) => {
? ? ? ? ? this.setState({showCalender: true});
? ? ? ? ? console.log('it is button')
? ? ? ? ? // e.stopPropagation();
? ? ? ? }}
? ? ? />
? ? ? <Calendar isShow={this.state.showCalender}></Calendar>
? ? </div>);
? }
}
其他的處理方法妹卿,也就是input也使用原生事件來阻止冒泡,或者使用stopImmediatePropagation
(4)合成事件執(zhí)行過程
React不會將事件處理函數(shù)直接綁定到真實(shí)的節(jié)點(diǎn)上蔑鹦,而是把所有的事件綁定到結(jié)構(gòu)的最外層夺克,使用一個統(tǒng)一的事件監(jiān)聽器。
這個監(jiān)聽器維持了一個映射嚎朽,保存所有組件內(nèi)部的事件監(jiān)聽和處理函數(shù)铺纽。當(dāng)事件發(fā)生時,首先被這個統(tǒng)一的事件監(jiān)聽器處理哟忍,
然后在映射里找到真正的事件處理函數(shù)并調(diào)用狡门。
合成事件分為以下三個主要過程:
一 事件注冊
所有事件都會注冊到document上,擁有統(tǒng)一的回調(diào)函數(shù)dispatchEvent來執(zhí)行事件分發(fā)
二 事件合成
從原生的nativeEvent對象生成合成事件對象锅很,同一種事件類型只能生成一個合成事件Event其馏,如onclick這個類型的事件,dom上所有帶有通過jsx綁定的onClick的回調(diào)函數(shù)都會按順序(冒泡或者捕獲)會放到Event._dispatchListeners 這個數(shù)組里爆安,后面依次執(zhí)行它
三 事件派發(fā)
每次觸發(fā)事件都會執(zhí)行根節(jié)點(diǎn)上 addEventListener 注冊的回調(diào)叛复,也就是 ReactEventListener.dispatchEvent 方法,事件分發(fā)入口函數(shù)。該函數(shù)的主要業(yè)務(wù)邏輯如下:
1. 找到事件觸發(fā)的 DOM 和 React Component
2. 從該 React Component褐奥,調(diào)用 findParent 方法咖耘,遍歷得到所有父組件,存在數(shù)組中撬码。
3. 從該組件直到最后一個父組件儿倒,根據(jù)之前事件存儲,用 React 事件名 + 組件 key呜笑,找到對應(yīng)綁定回調(diào)方法义桂,執(zhí)行,詳細(xì)過程為:
4. 根據(jù) DOM 事件構(gòu)造 React 合成事件蹈垢。
5. 將合成事件放入隊(duì)列慷吊。
6. 批處理隊(duì)列中的事件(包含之前未處理完的,先入先處理)
React合成事件的冒泡并不是真的冒泡曹抬,而是節(jié)點(diǎn)的遍歷溉瓶。
(5)React事件處理的特性與優(yōu)點(diǎn)
React的事件系統(tǒng)和瀏覽器事件系統(tǒng)相比,主要增加了兩個特性:事件代理和事件自動綁定
1谤民、事件代理
1. 區(qū)別于瀏覽器事件處理方式堰酿,React并未將事件處理函數(shù)與對應(yīng)的DOM節(jié)點(diǎn)直接關(guān)聯(lián),而是在頂層使用了一個全局事件監(jiān)聽器監(jiān)聽所有的事件张足;
2. React會在內(nèi)部維護(hù)一個映射表記錄事件與組件事件處理函數(shù)的對應(yīng)關(guān)系触创;
3. 當(dāng)某個事件觸發(fā)時,React根據(jù)這個內(nèi)部映射表將事件分派給指定的事件處理函數(shù)为牍;
4. 當(dāng)映射表中沒有事件處理函數(shù)時哼绑,React不做任何操作;
5. 當(dāng)一個組件安裝或者卸載時碉咆,相應(yīng)的事件處理函數(shù)會自動被添加到事件監(jiān)聽器的內(nèi)部映射表中或從表中刪除抖韩。
2、事件自動綁定
1. 在JavaScript中創(chuàng)建回調(diào)函數(shù)時疫铜,一般要將方法綁定到特定的實(shí)例茂浮,以保證this的正確性;
2. 在React中壳咕,每個事件處理回調(diào)函數(shù)都會自動綁定到組件實(shí)例(使用ES6語法創(chuàng)建的例外)席揽;
注意:事件的回調(diào)函數(shù)被綁定在React組件上,而不是原始的元素上谓厘,即事件回調(diào)函數(shù)中的
this所指的是組件實(shí)例而不是DOM元素幌羞;
3、合成事件
1. 與瀏覽器事件處理稍微有不同的是庞呕,React中的事件處理程序所接收的事件參數(shù)是被稱為“合成事件(SyntheticEvent)”的實(shí)例新翎。
合成事件是對瀏覽器原生事件跨瀏覽器的封裝,并與瀏覽器原生事件有著同樣的接口住练,如stopPropagation(),preventDefault()等地啰,并且
這些接口是跨瀏覽器兼容的。
2. 如果需要使用瀏覽器原生事件讲逛,可以通過合成事件的nativeEvent屬性獲取
React在事件處理的優(yōu)點(diǎn):
1. 幾乎所有的事件代理(delegate)到 document 亏吝,達(dá)到性能優(yōu)化的目的
2. 對于每種類型的事件,擁有統(tǒng)一的分發(fā)函數(shù) dispatchEvent
3. 事件對象(event)是合成對象(SyntheticEvent)盏混,不是原生的
4. react內(nèi)部事件系統(tǒng)實(shí)現(xiàn)可以分為兩個階段: 事件注冊蔚鸥、事件分發(fā),幾乎所有的事件均委托到document上许赃,而document上事件的回調(diào)函數(shù)只有一個: ReactEventListener.dispatchEvent止喷,然后進(jìn)行相關(guān)的分發(fā)
5. 對于冒泡事件,是在 document 對象的冒泡階段觸發(fā)混聊。對于非冒泡事件弹谁,例如focus,blur句喜,是在 document 對象的捕獲階段觸發(fā)预愤,最后在 dispatchEvent 中決定真正回調(diào)函數(shù)的執(zhí)行