ReactWeb前端之路React Native开发经验集

15、React系列之--React 事件系统

2017-05-03  本文已影响1059人  TigerChain

版权声明:本文为博主原创文章,未经博主允许不得转载。

PS:转载请注明出处
作者:TigerChain
地址:http://www.jianshu.com/p/99dc37f9edf3
本文出自TigerChain简书

React 教程系列

教程简介

本篇教程适合初学者,老鸟直接略过,如果有误,欢迎指出,谢谢。

eventsys.gif

一、事件和事件系统?

1、什么事件?

事件是一个宏观的概念,在各个领域中含义也不太一样。一般来说 事件就是:比较重大,对一定的人群会产生一定影响的事情。

但是在编程中的事件,是一个抽像的概念,可以理解就是干什么(动作),比如:点击一个按钮是一个事件,刷新网页是一个事件,拖拽,移动等等都是是事件。

事件不能单独工作,事件和事件源,监听器三者联合起来才可以工作,所以我们也可以说,事件的三剑客就是,事件、事件源和监听器。

举个栗子!!!

比如点击一个按钮显示出一句话:" Hello React " ! ,那么分析一下这一事件的过程。

事件:点击
事件源:按钮(发生事件的对象也叫事件的产生器)
监听器:监听着事件处理结果,一般是一个回调函数。

2、什么是事件系统?

软件开发中的事件系统也就是事件处理系统,它包含事件监听,事件分发,事件处理回调等等一系列过程。(个人理解,如有不同理解可以指出)。每个开发平台有自己不同的事件处理机制。

二、React 事件系统

1、跨浏览器使用

React 标准化了事件对象,和浏览器本地事件是是同一个接口( React 也就是把浏览器的事件包装了一把而已),所以它可以工作在所有的浏览器中,可以直接在所有的浏览器使用 React 的事件。

2、React 的事件分类

1、合成事件

一、React 事件和 HTML 事件

在 React 中绑定事件和 HTML 中绑定事件类似,但是还是有以下不同。

举个例子

在 HTML 中一个按钮的点击事件

<button onclick="showImg()">显示图片</button>

在 React 中一个按钮的点击事件

<button onClick={showImg}>显示图片</button>

PS:
关于 HTML 事件可以看这里:http://www.runoob.com/jsref/dom-obj-all.html

二、合成事件

用官网的话说:React 实现了一个“合成事件”层(synthetic event system),这个事件模型保证了和 W3C 标准保持一致,所以不用担心有什么诡异的用法,并且这个事件层消除了 IE 与 W3C 标准实现之间的兼容问题。

绑定事件,在 React 合成事件中有三种绑定事件的方式:

格式:

<Component 事件={this.方法.bind(this)}></Component>

实例

import React ,{Component} from 'react';

class EventApp extends Component {
    
    render(){
        return
          (<div>
            <button onClick={this._clickMe.bind(this)}>点击我</button>
          </div>
          ) ; 
    }
   
  _clickMe(){
    alert("组件绑定事件实现方法") ;
   }
        
}

PS:每次点击的时候都要重新绑定一个函数,我们知道函数的创建和销毁是需要开销的,所以这种方式对性能有影响。

格式:

constructor(props){
    super(props) ;
    this.方法 = this.方法.bind(this,'event','args') ;
    // event(事件名) 和 args(参数) 不是必须的。
}
<Component 事件={this.方法}></Component>

实例

import React ,{Component} from 'react';

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("构造方法绑定事件实现方法") ;
   }
        
}

使用构造方法绑定事件,只需要绑定一次。而不用每次使用的时候都去绑定。

格式:

  <Component 事件={(e) => this.方法(e,args)}</Component> 
  //其中 args (参数)不是必须的。

实例

import React ,{Component} from 'react';

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);
   }
        
}

PS:使用箭头函数系统就默认把 this 绑定到了 _clickMe 方法中,但是需要注意一点,由于箭头函数绑定是定义在 redner 方法中的,组件的每一次渲染都会创建一个新的箭头函数 [ React 中渲染是经常会发生的]。这种方式和组件上绑定是一个道理,对性能有影响。

总结: 综上所述,我个人建议使用在构造方法中绑定事件的方法这种方式。

三、合成事件绑定事件的处理函数

React 合成事件绑定事件处理的函数涉及到鼠标类,触摸类,键盘类,表单和焦点等。

具体可以看官方 Supported Events 这一小节: https://facebook.github.io/react/docs/events.html,这没有什么好说的。

注意:经过上面的分析,我们知道 React 的事件绑定有三种方式,但是我们要注意一点,我们不能直接在组件中监听事件。因为组件是 DOM 元素的包装器,如果在组件中直接监听事件,那么到底事件应该是对应那个 DOM 元素的,是一个 button 的还是一张图片的,你就分不清楚了。这样你也分不清楚它和 props 有啥区别。

比如:定义一个含有按钮的子组件 ShowPlus.js 。然后在父组件中监听事件。

1、错误做法

# ShowPlus.js

import React, { Component, PropTypes } from 'react';

export default class ShowPlus extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
    <div>
      <button> 点我 </button>
    </div>);
  }
}

父组件,姑且叫 FatherCom.js

# FatherCom.js

import React, { Component, PropTypes } from 'react';

import ShowPlus from './ShowPlus.js' ;

export default class FatherCom extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
    <div>
        <ShowPlus onClick={this._addPlus}/>
    </div>);
  }

   _addPlus(){
    alert("this is a plus") ;
  }
}

以上方式是错误的,你把按钮点死它也不会调用的,所以我们不要直接在一个自定义组件上添加监听器。

那么我们如何处理这种情况呢,看过 props 这一节的朋友很快就会有答案的,我们可以把事件处理器当作 props 传递到子组件即可。

2、正确的做法

# ShowPlus.js

import React, { Component, PropTypes } from 'react';

export default class ShowPlus extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
    <div>
      <button onClick={this.props.clickHandler}> 点我 </button>
    </div>);
  }
}

# FatherCom.js

import React, { Component, PropTypes } from 'react';

import ShowPlus from './ShowPlus.js' ;

export default class FatherCom extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
    <div>
        <ShowPlus clickHandler={this._addPlus}/>
    </div>);
  }

   _addPlus(){
    alert("this is a plus") ;
  }
}

这样我们就采用一个变通的方法,把事件处理器当作一个 props 传递给子组件,然后在组件内部,我们把事件监听到一个 DOM 元素上,并将事件处理设置为我们传递过来的 props 就解决了这种问题。

2、原生事件

原生事件就是浏览器所对应的事件,一般情况下,我们使用 React 提供的合成事件就能满足大部分需求,但是有些情况下,我们不得不使用原生事件,所以必要时合成事件和原生事件要搭配着使用。

什么是原生事件?

比如,你在 componentDidMount 方法中通过 addEventListener 绑定的事件就是原生事件。

由于不是所有的 DOM 都有合成事件的对应物。我们不妨试一下"自定义"一个事件。

1、自定义事件

错误的例子:

import React, { Component, PropTypes } from 'react';

/**
 * 自定义事件
 */
export default class CustomEvent extends Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
      <button onCusTomAlert={this._customAlert.bind(this)}>自定义事件</button>
    </div>);
  }

  _customAlert(e){
    alert("custom event") ;
  }
}

在上面我们自定义了一个 onCusTomAlert 事件,这是 React 中是行不通的,对于 React 不能识别的事件,我们就要使用原生传统的事件方式 addEventListener 处理。修改上面的代码。

正确的例子:

import React, { Component, PropTypes } from 'react';

/**
 * 自定义事件
 */
export default class CustomEvent extends Component {
  constructor(props) {
    super(props);
  }
  // 组件挂载之后注册监听器
  componentDidMount(){
    window.addEventListener("click", this._customAlert);
  }
  //组件卸载之后移除监听器
  componentWillUnmount(){
    window.removeEventListener("click",this._customAlert) ;
  }
  render() {
    return (
      <div>
      <button>自定义事件</button>
    </div>);
  }

  _customAlert(e){
    alert("custom event") ;
  }
}

以上只是一个例子而已,当然对于按钮来说是有 onClick 事件的。

2、原生事件一定要手动移除

对于原生事件,我们一定要手动移除,否则很可能出现内存泄露的问题,一般情况下,我们在 componentDidMount() 方法中注册原生事件,在 componentWillUnmount() 方法中移除事件。而使用合成事件,则不需要,因为 React 内部已经帮我们处理好了。

3、合成事件和原生事件

对合成事件和原生事件,我们尽量不要合成事件和原生事件混用,只是对于无法使用 React 合成事件解决问题的这一场景,我们才需要去使用原生事件。

1、原生事件的事件冒泡和事件捕获

先来一张图来看看 js 冒泡事件和捕获原型

jsevent.png

事件捕获就相当于拿个鱼叉捕鱼一样,从水面上面举起叉子插入到水面水鱼所在的位置,在原生 JS 中,就是从 html 所外层的元素向内层元素触发。

事件冒泡和事件捕获刚好相反,就想当鱼从水泡吹出一个泡泡到水面上,是从 html 内层元素到外层元素触发的。

有一个 div 里面有个 button

<div>
    <button>点我</button>
</div>

假设这两个元素都绑定了 onclick 事件,如果用户点击了 button ,它在 div 和 p 上都触发了 click 事件,那么执行事件的顺序是如何呢?

事件冒泡:button [子元素先触发] 先触发事件,再到 div

事件捕获:div 先触发事件,再到 button

但是在不同的浏览器中处理是不同的,IE 浏览器仅支持事件冒泡,Netscape 支持事件冒泡。

开发人员可以自己选择绑定事件采用冒泡还是捕获,可以通过方法 addEventListener 的第三个参数来指定。

el.addEventListener('someEvent',doSomething,true)

第三个参数如果为 true 就是事件捕获,如果是 false 就是事件冒泡。

但是 IE 浏览器除外( IE 只支持事件冒泡),也不支持 addEventListener 函数,他有一个自己的函数叫 el.attachEvent("onclick", doSomething);

2、事件传播是可以阻止的(原生事件)

(1)、禁止事件冒泡

(2)、禁止默认事件

具体可以看这篇文章

3、注意点

以上就是 React 的事件系统,信息量还是比较大的,希望大家多多实践。

Demo 地址: https://github.com/githubchen001/react-lesson/tree/master/lesson03/12-eventsys

据说点喜欢的人都能成为大神。

上一篇 下一篇

猜你喜欢

热点阅读