淺析React之事件系統(二)

上篇文章中,咱們談到了React事件系統的實現方式,和在React中使用原生事件的方法,那麼這篇文章咱們來繼續分析下,看看React中合成事件和原生事件混用的各類狀況。javascript

上一個例子

在上篇文章中,咱們舉了個例子。爲了防止你們不記得,咱們來看看那個例子的代碼。java

class App extends React.Component {

  constructor(props){
    super(props);
    
    this.state = {
      show: false
    }
    
    this.handleClick = this.handleClick.bind(this)
    this.handleClickImage = this.handleClickImage.bind(this);
  }
  
  handleClick(){
   this.setState({
     show: true
   })
  }
  
  componentDidMount(){
    document.body.addEventListener('click', e=> {
      this.setState({
        show: false
      })
    })
  }
  
  componentWillUnmount(){
    document.body.removeEventListener('click');
  }
  
  handleClickImage(e){
    console.log('in this ')
    e.stopPropagation();
  }
  
  render(){
    return (
      <div className="container">
        <button onClick={this.handleClick}>Open Image</button>
          <div className="img-container" style={{ display: this.state.show ? 'block': 'none'}} onClick={this.handleClickImage}>
            <img src="http://blog.addthiscdn.com/wp-content/uploads/2014/11/addthis-react-flux-javascript-scaling.png" />
          </div>
      </div>
    )
  }
}
ReactDOM.render(<App />, document.getElementById('root'));

這有什麼問題呢? 問題就在於,若是咱們點擊image的內部依舊能夠收起Image,那麼這是爲何呢?這是由於咱們及時點擊了Image的內部,body上綁定的事件處理器依舊會執行,這樣就讓咱們的image收起來了。那咱們若是不想讓image收起來改怎麼作呢?react

首先的想法是中止冒泡,若是咱們在img-container中就中止冒泡了是否是就可讓image不消失了呢?好比這樣:segmentfault

...
handleClickImage(e){
    e.preventDefault();
    e.stopPropagation();
  }
  
  render(){
    return (
      <div className="container">
        <button onClick={this.handleClick}>Open Image</button>
          <div className="img-container" style={{ display: this.state.show ? 'block': 'none'}} onClick={this.handleClickImage}>
            <img src="http://blog.addthiscdn.com/wp-content/uploads/2014/11/addthis-react-flux-javascript-scaling.png" />
          </div>
      </div>
    )
  }
...

Open In CodePenthis

在這裏咱們定義一個handleClickImage的方法,在其中咱們執行取消默認行爲和中止冒泡。那是彷佛效果並非咱們想要的。由於阻止React事件冒泡的行爲只能用於React合成事件中,無法阻止原生事件的冒泡。一樣用React.NativeEvent.stopPropagation()也是沒法阻止冒泡的。code

如何解決這樣的問題呢?首先,儘可能的避免混用合成事件和原生事件。須要注意的點是:component

  1. 阻止react 合成事件冒泡並不會阻止原生時間的冒泡,從上邊的例子咱們已經看到了,及時使用stopPropagation也是沒法阻止原生時間的冒泡的。cdn

  2. 第二點須要注意的是,取消原生時間的冒泡會同時取消React Event。而且原生事件的冒泡在react event的觸發和冒泡以前。同時React Event的建立和冒泡是在原生事件冒泡到最頂層的component以後的。咱們來看這個例子:blog

class App extends React.Component {
  
  render(){
   return <GrandPa />;
  }
}

class GrandPa extends React.Component {
  constructor(props){
      super(props);
      this.state = {clickTime: 0};
      this.handleClick = this.handleClick.bind(this);
  }
  
 handleClick(){
   console.log('React Event grandpa is fired');
  this.setState({clickTime: new Date().getTime()})
};
  
  componentDidMount(){
    document.getElementById('grandpa').addEventListener('click',function(e){
      console.log('native Event GrandPa is fired');
    })
  }
  
  render(){
    return (
      <div id='grandpa' onClick={this.handleClick}>
        <p>GrandPa Clicked at: {this.state.clickTime}</p>
        <Dad />
      </div>
    )
  }
}

class Dad extends React.Component {
  constructor(props){
    super(props);
    this.state = {clickTime:0};
    this.handleClick=this.handleClick.bind(this);
  }
  
  componentDidMount(){
    document.getElementById('dad').addEventListener('click',function(e){
      console.log('native Event Dad is fired');
      e.stopPropagation();
    })
  }
  
  handleClick(){
    console.log('React Event Dad is fired')
    this.setState({clickTime: new Date().getTime()})
  }
  
  render(){
    return (
      <div id='dad' onClick={this.handleClick}>
       <p>Dad Clicked at: {this.state.clickTime}</p>
        <Son />
      </div>
     )
  }
}

class Son extends React.Component {
  constructor(props){
    super(props);
    this.state = {clickTime:0};
    this.handleClick=this.handleClick.bind(this);
  }
  
  handleClick(){
    console.log('React Event Son is fired');
    this.setState({clickTime: new Date().getTime()})
  }
  
  componentDidMount(){
    document.getElementById('son').addEventListener('click',function(e){
      console.log('native Event son is fired');
    })
  }
  
  render(){
    return (
      <div id="son">
       <p onClick={this.handleClick}>Son Clicked at: {this.state.clickTime} </p>
      </div>
     )
  }
}

ReactDOM.render(<App />, document.getElementById('root'));

Open in CodePenflux

在這個例子中咱們有三個component,Son Dad,Grandpa。同時定義了React Event handler 和 native event handler,並在Dad的native Event handler中stopPropagation,當咱們點擊Son or Dad component的時候會發現,React Event handler並無被trigger。
console裏的output爲:

"native Event son is fired"
"native Event Dad is fired"

這就說明native Event的中止冒泡能夠阻斷全部的React Event。因此即便咱們是在Dad上中止冒泡的,依舊阻斷了Son上的React Event。

同時若是咱們把dad上的stopPropagation remove掉咱們會看到以下結果:

"native Event son is fired"
"native Event Dad is fired"
"native Event GrandPa is fired"
"React Event Son is fired"
"React Event Dad is fired"
"React Event grandpa is fired"

這就說明React的合成時間是在原生事件冒泡到最頂層組件結束後才建立和冒泡的,也是符合React的原理,由於在是實現的時候React只是將一個Event listener 掛在了最頂層的組件上,其內部一套本身的機制進行事件的管理。

淺析React之事件系統(一)

相關文章
相關標籤/搜索