react事件機制

看風景就發表於2018-09-12

1. react的事件是合成事件((Synethic event),不是原生事件

<button onClick={this.handleClick}></button>

<input value={this.state.name} onChange={this.handleChange} />

合成事件與原生事件的區別

1. 寫法不同,合適事件是駝峰寫法,而原生事件是全部小寫
2. 執行時機不同,合適事件全部委託到document上,而原生事件繫結到DOM元素本身
3. 合成事件中可以是任何型別,比如this.handleClick這個函式,而原生事件中只能是字串

2. react合成事件執行過程

 

3. 事件繫結this寫法

React的ES5寫法,事件繫結可以自動繫結到元件例項上,而ES6寫法卻不能,必須使用一些特定的寫法。

1. 建構函式bind this

class EventApp extends Component {
    constructor(props){
        super(props) ;
        this._clickMe = this._clickMe.bind(this) ;
    }
    render(){
        return
          (<div>
            <button onClick={this._clickMe}>點選我</button>
          </div>
          ) ; 
    }
    _clickMe(){
        alert("構造方法繫結事件實現方法") ;
   }   
}

只在構造元件時繫結一次,最佳的方式

2. 元素上bind this

class EventApp extends Component {
    render(){
        return
          (<div>
            <button onClick={this._clickMe.bind(this)}>點選我</button>
          </div>
          ) ; 
    }
    _clickMe(){
        alert("元件繫結事件實現方法") ;
    }
}

每次click會重新生成一個繫結函式,效率不佳

3. 使用箭頭函式

class EventApp extends Component {
    constructor(props){
        super(props) ;
    }
    render(){
        return
          (<div>
              <button onClick={(e) => this._clickMe(e,"使用箭頭函式繫結")}>使用箭頭函式繫結事件</button> <p/>
          </div>
          ) ; 
    }
    _clickMe(e,args){
        alert("箭頭函式繫結事件實現方法") ;
        alert(args);
        alert(e);
    }
}

這種箭頭函式定義在render中,元件每渲染一次都會建立一次新的箭頭函式,對效能有影響

4. React元件中使用原生事件

由於原生事件繫結在真實DOM上,所以一般是在componentDidMount中或ref回撥函式中進行
繫結操作,在componentWillUnmount階段進行解綁操作,以避免記憶體洩漏。

class ReactEvent extends Component {
    componentDidMount() {
        //獲取當前真實DOM元素
        const thisDOM = ReactDOM.findDOMNode(this);
        //或者
        const thisDOM = this.refs.con;
        thisDOM.addEventListener('click',this.onDOMClick,false);
    }
    componentWillUnmount() {
        //解除安裝時解綁事件,防止記憶體洩漏
        const thisDOM = ReactDOM.findDOMNode(this);
        thisDOM.removeEventListener('click',this.removeDOMClick);
    }
    onDOMClick(e){
        console.log(e)
    }
    render(){
        return(
            <div ref="con">
                單擊原始事件觸發
            </div>
        )
    }
}
export default ReactEvent

在componentDidMount週期中,使用addEventListener直接繫結即可,dom元素使用ref或者
ReactDOM.findDOMNode來獲取。

合成事件和原生事件可以混合使用,但是儘量避免這種情況出現,因為容易導致混亂,則某些情況下
可以使用。合成事件和DOM事件混合使用,觸發順序是:

1. 先執行原生事件,事件冒泡至document,再執行合成事件
2. 如果是父子元素,觸發順序為 子元素原生事件 -> 父元素原生事件 -> 子元素合成事件 -> 父元素合成事件

如下例子:

class ReactEvent extends Component {
    constructor(props){
        super(props)
        this.state = {
            value: ''
        }
        this.onReactClick = this.onReactClick.bind(this)
        this.onReactChildClick = this.onReactChildClick.bind(this)
    }
    componentDidMount() {
        //獲取當前真實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("父元件原生事件繫結事件觸發")
    }
 
    onReactClick(e){
        console.log("父元件React合成事件繫結事件觸發")
    }
    onDOMChildClick(e){
        e.stopPropagation()
        console.log("子元素原生事件繫結事件觸發")
    }
    onReactChildClick(e){
        console.log("子元素React合成事件繫結事件觸發")
    }
    render(){
            return(
                <div onClick={this.onReactClick}>                
                    父元素單擊事件觸發
                    <button className="child" onClick={this.onReactChildClick}>子元素單擊事件觸發</button>
                </div>
            )
    }
}
export default ReactEvent

通過設定原生事件繫結為冒泡階段呼叫,且每次測試單擊子元素按鈕:

1.在子元素原生事件程式中阻止事件傳播,則列印出:

子元素原生事件繫結事件觸發;

2.在父元素元素事件程式中阻止事件傳播,則列印出:

子元素原生事件繫結事件觸發
父元件原生事件繫結事件觸發

3.在子元素React合成事件onClick中阻止事件傳播,則列印出:

子元素原生事件繫結事件觸發
父元件原生事件繫結事件觸發
子元素React合成事件繫結事件觸發

4.在父元素React合成事件onClick中阻止事件傳播,則列印出:

子元素原生事件繫結事件觸發
父元件原生事件繫結事件觸發
子元素React合成事件繫結事件觸發
父元件React合成事件繫結事件觸發

可以看到若不阻止事件傳播每次(單擊子元素)事件觸發流程是:
Document->子元素(原生事件觸發)->父元素(原生事件)->回到Document->React子元素合成事件監聽器觸發 ->React父元素合成事件監聽器觸發

5. 合成事件與阻止冒泡

基本原則

阻止合成事件的冒泡不會阻止原生事件的冒泡,但是阻止原生事件的冒泡會阻止合成事件的冒泡。

1. 阻止合成事件冒泡,用e.stopPropagation()

例如:

render() {
        return 
            <div onClick={this.outClick}>
                <button onClick={this.onClick}> 測試click事件 </button>
            </div>
}

outClick是外層合成事件,用e.stopPropagation會阻止其執行,但是不能阻止原生事件,例如document上用
addEventListener繫結的事件

2. 阻止原生事件和合成事件冒泡(因為阻止了原生事件就會阻止合成事件),用e.nativeEvent.stopImmediatePropagation();
3. 在document或body上註冊的原生事件方法,可以通過e.target判斷來阻止冒泡事件的執行

例如存在如下的業務場景: 點選input框展示日曆,點選文件其他部分,日曆消失,程式碼如下:

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事件不往下執行
      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

7. 合成事件執行過程

React不會將事件處理函式直接繫結到真實的節點上,而是把所有的事件繫結到結構的最外層,使用一個統一的事件監聽器。
這個監聽器維持了一個對映,儲存所有元件內部的事件監聽和處理函式。當事件發生時,首先被這個統一的事件監聽器處理,
然後在對映裡找到真正的事件處理函式並呼叫。

合成事件分為以下三個主要過程:

一 事件註冊

所有事件都會註冊到document上,擁有統一的回撥函式dispatchEvent來執行事件分發

二 事件合成

從原生的nativeEvent物件生成合成事件物件,同一種事件型別只能生成一個合成事件Event,如onclick這個型別的事件,dom上所有帶有通過jsx繫結的onClick的回撥函式都會按順序(冒泡或者捕獲)會放到Event._dispatchListeners 這個陣列裡,後面依次執行它

三 事件派發

每次觸發事件都會執行根節點上 addEventListener 註冊的回撥,也就是 ReactEventListener.dispatchEvent 方法,事件分發入口函式。該函式的主要業務邏輯如下:
1. 找到事件觸發的 DOM 和 React Component
2. 從該 React Component,呼叫 findParent 方法,遍歷得到所有父元件,存在陣列中。
3. 從該元件直到最後一個父元件,根據之前事件儲存,用 React 事件名 + 元件 key,找到對應繫結回撥方法,執行,詳細過程為:
4. 根據 DOM 事件構造 React 合成事件。
5. 將合成事件放入佇列。
6. 批處理佇列中的事件(包含之前未處理完的,先入先處理)

React合成事件的冒泡並不是真的冒泡,而是節點的遍歷。

8. React事件處理的特性

React的事件系統和瀏覽器事件系統相比,主要增加了兩個特性:事件代理和事件自動繫結

1、事件代理

1. 區別於瀏覽器事件處理方式,React並未將事件處理函式與對應的DOM節點直接關聯,而是在頂層使用了一個全域性事件監聽器監聽所有的事件;
2. React會在內部維護一個對映表記錄事件與元件事件處理函式的對應關係;
3. 當某個事件觸發時,React根據這個內部對映表將事件分派給指定的事件處理函式;
4. 當對映表中沒有事件處理函式時,React不做任何操作;
5. 當一個元件安裝或者解除安裝時,相應的事件處理函式會自動被新增到事件監聽器的內部對映表中或從表中刪除。

2、事件自動繫結

1. 在JavaScript中建立回撥函式時,一般要將方法繫結到特定的例項,以保證this的正確性;
2. 在React中,每個事件處理回撥函式都會自動繫結到元件例項(使用ES6語法建立的例外);

注意:事件的回撥函式被繫結在React元件上,而不是原始的元素上,即事件回撥函式中的
this所指的是元件例項而不是DOM元素;

3、合成事件

1. 與瀏覽器事件處理稍微有不同的是,React中的事件處理程式所接收的事件引數是被稱為“合成事件(SyntheticEvent)”的例項。
合成事件是對瀏覽器原生事件跨瀏覽器的封裝,並與瀏覽器原生事件有著同樣的介面,如stopPropagation(),preventDefault()等,並且
這些介面是跨瀏覽器相容的。
2. 如果需要使用瀏覽器原生事件,可以通過合成事件的nativeEvent屬性獲取

9. React在事件處理的優點

1. 幾乎所有的事件代理(delegate)到 document ,達到效能優化的目的
2. 對於每種型別的事件,擁有統一的分發函式 dispatchEvent
3. 事件物件(event)是合成物件(SyntheticEvent),不是原生的
4. react內部事件系統實現可以分為兩個階段: 事件註冊、事件分發,幾乎所有的事件均委託到document上,而document上事件的回撥函式只有一個: ReactEventListener.dispatchEvent,然後進行相關的分發
5. 對於冒泡事件,是在 document 物件的冒泡階段觸發。對於非冒泡事件,例如focus,blur,是在 document 物件的捕獲階段觸發,最後在 dispatchEvent 中決定真正回撥函式的執行

 

 


參考: https://www.jianshu.com/p/99dc37f9edf3
    https://blog.csdn.net/qq_38160012/article/details/80679420
    https://zhuanlan.zhihu.com/p/26742034
    http://www.open-open.com/lib/view/open1488868982317.html
    https://segmentfault.com/a/1190000013364457

相關文章