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

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

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

React 教程系列

教程简介

  • 1、阅读对象

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

eventsys.gif

一、事件和事件系统?

1、什么事件?

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

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

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

举个栗子!!!

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

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

2、什么是事件系统?

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

二、React 事件系统

1、跨浏览器使用

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

2、React 的事件分类

1、合成事件

一、React 事件和 HTML 事件

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

  • 1、React 事件的命名是驼峰标志,比如: onClick 而不能是 onclick。
  • 2、在 JSX 中你可以传一个方法去处理函数,而不是一个字符串。
  • 3、React事件并没有原生的绑定在真实的DOM上,而是使用了 行为委托 方式实现事件机制。

举个例子

在 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 合成事件中有三种绑定事件的方式:

  • 1、组件上绑定。

格式:

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

  • 2、构造方法中绑定。

格式:

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

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

  • 3、使用箭头函数。

格式:

  <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)、禁止事件冒泡

  • 1、在 W3C 中,使用 stopPropagation() 方法。
  • 2、在 IE 下,设置 window.event.cancelBubble = true。

(2)、禁止默认事件

  • 1、阻止默认浏览器动作(W3C)使用, e.preventDefault()。

  • 2、IE中阻止函数器默认动作的方式 , window.event.returnValue = false。

具体可以看这篇文章

3、注意点

  • 1、阻止 React 合成事件冒泡,并不能阻止原生事件的冒泡,就算使用 stopPropagation 也无法阻止原生事件的冒泡。
  • 2、取消原生事件的冒泡也会同时取消 React 事件,并且原生事件的冒泡在 React 事件的触发和冒泡之前。

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

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,397评论 25 707
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,809评论 1 18
  • 本文通过一个简短的实例&控制台调试,了解react事件处理的全过程。下面是测试用代码,使用控制台可以清晰看到函数执...
    溪离欣洛阅读 1,197评论 2 2
  • 雨中东湖七山连纵 本来一个月前约好十二人的队伍,因种种,只剩我母女俩赴约。前一天临睡前,米爸还要求我们别去了。没应...
    Friz阅读 250评论 0 0
  • 舟行两广地,夜泊银沙滩。闻钟鸡冠寺,潮汐迟迟归。
    好客坊阅读 255评论 1 0