react事件機(jī)制

(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í)行

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市咳胃,隨后出現(xiàn)的幾起案子植康,更是在濱河造成了極大的恐慌,老刑警劉巖展懈,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件销睁,死亡現(xiàn)場離奇詭異,居然都是意外死亡存崖,警方通過查閱死者的電腦和手機(jī)榄攀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來金句,“玉大人檩赢,你說我怎么就攤上這事∥ツ” “怎么了贞瞒?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長趁曼。 經(jīng)常有香客問我军浆,道長,這世上最難降的妖魔是什么挡闰? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任乒融,我火速辦了婚禮掰盘,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赞季。我一直安慰自己愧捕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布申钩。 她就那樣靜靜地躺著次绘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撒遣。 梳的紋絲不亂的頭發(fā)上邮偎,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機(jī)與錄音义黎,去河邊找鬼禾进。 笑死,一個胖子當(dāng)著我的面吹牛廉涕,可吹牛的內(nèi)容都是我干的命迈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼火的,長吁一口氣:“原來是場噩夢啊……” “哼壶愤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起馏鹤,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤征椒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后湃累,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勃救,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年治力,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒙秒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡宵统,死狀恐怖晕讲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情马澈,我是刑警寧澤瓢省,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站痊班,受9級特大地震影響勤婚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涤伐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一馒胆、第九天 我趴在偏房一處隱蔽的房頂上張望缨称。 院中可真熱鬧,春花似錦祝迂、人聲如沸睦尽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掌动,卻和暖如春四啰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粗恢。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工柑晒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人眷射。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓匙赞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親妖碉。 傳聞我的和親對象是個殘疾皇子涌庭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容