深入浅出React(五)(React组件事件详解)

时间:2021-05-09 16:18:14

React组件事件响应

  • React在构建虚拟DOM的同时,还构建了自己的事件系统;且所有事件对象和W3C规范
    保持一致。
  • React的事件系统和浏览器事件系统相比,主要增加了两个特性:事件代理、和事件自动绑定

1、事件代理

  1. 区别于浏览器事件处理方式,React并未将事件处理函数与对应的DOM节点直接关联,而是在顶层使用
    了一个全局事件监听器监听所有的事件;
  2. React会在内部维护一个映射表记录事件与组件事件处理函数的对应关系;
  3. 当某个事件触发时,React根据这个内部映射表将事件分派给指定的事件处理函数;
  4. 当映射表中没有事件处理函数时,React不做任何操作;
  5. 当一个组件安装或者卸载时,相应的事件处理函数会自动被添加到事件监听器的内部映射表中或从表中删除。

2、事件自动绑定

  1. 在JavaScript中创建回调函数时,一般要将方法绑定到特定的实例,以保证this的正确性;

2.在React中,每个事件处理回调函数都会自动绑定到组件实例(使用ES6语法创建的例外);

  1. 注意:事件的回调函数被绑定在React组件上,而不是原始的元素上,即事件回调函数中的
    this所指的是组件实例而不是DOM元素;
  2. 了解更多React中的thisReact组件中的this

3、合成事件

与浏览器事件处理稍微有不同的是,React中的事件处理程序所接收的事件参数是被称为 “合成事件(SyntheticEvent)”的实例。
  1. 合成事件是对浏览器原生事件跨浏览器的封装,并与浏览器原生事件有着同样的接口,如stopPropagation(),preventDefault()等,并且
    这些接口是跨浏览器兼容的。
  2. 如果需要使用浏览器原生事件,可以通过合成事件的nativeEvent属性获取
  3. React合成事件原理

使用JSX,在React中绑定事件:

<button onClick={this.onClick}>
    单击触发react事件
</button>
  • React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行

深入浅出React(五)(React组件事件详解)

  1. 每个合成事件有以下通用属性:
    - boolean bubbles     - boolean cancelable     - DOMEventTarget currentTarget     - boolean defaultPrevented     - number eventPhase     - boolean isTrusted     - DOMEvent nativeEvent     - void preventDefault()     - boolean isDefaultPrevented()     - void stopPropagation()     - boolean isPropagationStopped()     - DOMEventTarget target     - number timeStamp     - string type
  • 注意:现版本React在事件处理程序通过中返回false停止传播,已不可用;
    取而代之的是需要手动调用e.stopPropagation()或e.preventDefalult().

React支持的常用事件

1、剪贴板事件

onCopy onCut onPaste

2、键盘事件

onKeyDown onKeyPress onKeyUp

3、焦点事件

onFocus onBlur

  • 这些焦点事件工作在 React DOM 中所有的元素上 ,不仅是表单元素。

4、表单事件

onChange onInput onSubmit

  • onChange事件经过React改良,内容改变时即可实时触发;而原生的需内容改变且失去焦点后触发才触发。

5、鼠标事件

onClick onContextMenu onDoubleClick 

onDrag onDragEnd onDragEnter onDragExit

onDragLeave onDragOver onDragStart onDrop

onMouseDown onMouseEnter onMouseLeave

onMouseMove onMouseOut onMouseOver onMouseUp 
  • onMouseEnter 和 onMouseLeave 事件从离开的元素传播到正在进入的元素,而不是普通的冒泡,并且没有捕获阶段;只有在鼠标指针穿过被选元素时,才会触发。
  • onMouseOut onMouseOver事件:不论鼠标指针穿过被选元素或其子元素,都会触发。

6、选择事件

onSelect

7、触摸事件

onTouchCancel onTouchEnd onTouchMove onTouchStart

8、UI事件

onScroll

9、滚轮事件

onWheel

10、图像事件

onLoad onError

11、动画事件

onAnimationStart onAnimationEnd onAnimationIteration

12、其他事件

onToggle

在React中使用原生事件

  • 由于原生事件需要绑定在真实DOM上,所以一般是在 componentDidMount阶段/ref的函数执行阶段进行绑定操作,在componentWillUnmount 阶段进行解绑操作以避免内存泄漏。
import React,{Component} from 'react';
import ReactDOM from 'react-dom'

class ReactEvent extends Component {

    componentDidMount() {
        //获取当前真实DOM元素
        const thisDOM = ReactDOM.findDOMNode(this);
        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> 单击原始事件触发 </div>
        )
    }
}
export default ReactEvent

合成事件和原生事件混合使用

  • 响应顺序
import React,{Component} from 'react';
import ReactDOM from 'react-dom'

class ReactEvent extends Component {
    constructor(props){
        super(props)
        this.state = {
            value: ''
        }
        this.onReactClick = this.onReactClick.bind(this)
    }
    componentDidMount() {
        //获取当前真实DOM元素
        const thisDOM = ReactDOM.findDOMNode(this);
        thisDOM.addEventListener('click',this.onDOMClick,false);

    }

    componentWillUnmount() {
        //卸载时解绑事件,防止内存泄漏
        const thisDOM = ReactDOM.findDOMNode(this);
        thisDOM.removeEventListener('click',this.removeDOMClick);
    }

    onDOMClick(e){
        console.log("原生事件绑定事件触发")
    }

    onReactClick(e){
        console.log("React合成事件绑定事件触发")
    }

    render(){
        return(
            <div onClick={this.onReactClick}> 单击事件触发 </div>
        )
    }
}
export default ReactEvent
  • 首先DOM事件监听器被执行,然后事件继续冒泡至document,合成事件监听器再被执行。
  • 即,最终控制台输出为:

  • 合成事件绑定事件触发

  • 原生事件绑定事件触发

  • 阻止冒泡
  1. 如果在onDOMClick中调用e.stopPropagtion()
    onDOMClick(e){
        e.stopPropagation()
        console.log("原生事件绑定事件触发")
    }
  • 由于DOM事件被阻止冒泡了,无法到达document,所以合成事件自然不会被触发,控制台输出就变成了:

    • 原生事件绑定事件触发
  • 再测试个复杂的例子
import React,{Component} from 'react';
import ReactDOM from 'react-dom'

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父元素合成事件监听器触发

其实,React合成事件封装的stopPropagtion函数在调用时给自己加了个isPropagationStopped的标记位来确定后续监听器是否执行。