15、React系列之 React 事件系统

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

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

React 教程系列

教程简介

  • 1、阅读对象

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

一、事件和事件系统?

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 事件可以看这里:www.runoob.com/jsref/dom-o…

二、合成事件

用官网的话说: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 这一小节: facebook.github.io/react/docs/…,这没有什么好说的。

注意:经过上面的分析,我们知道 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 冒泡事件和捕获原型

  • 事件捕获

事件捕获就相当于拿个鱼叉捕鱼一样,从水面上面举起叉子插入到水面水鱼所在的位置,在原生 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 地址: github.com/githubchen0…

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值