React-合成事件和原生事件的区别

7,066 阅读3分钟

导言

我最近在学校React事件绑定的时候,发现,React绑定有其自身的一套机制,就是合成事件。
在React中大概是这样绑定事件的

<div className="dome" onClick={this.handleClick}>

而普通的事件绑定是这样的:

<div class="dome" onclick="handleClick()">

React合成事件和原生事件的区别

React合成事件机制: React并不是将click事件直接绑定在dom上面,而是采用事件冒泡的形式冒泡到document上面,然后React将事件封装给正式的函数处理运行和处理。

React合成事件详细理解

如果DOM上绑定了过多的事件处理函数,整个页面响应以及内存占用可能都会受到影响。
React为了避免这类DOM事件滥用,同时屏蔽底层不同浏览器之间的事件系统的差异,实现了一个中间层 - SyntheticEvent

  1. 当用户在为onClick添加函数时,React并没有将Click绑定到DOM上面

  2. 而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装交给中间层 SyntheticEvent (负责所有事件合成)

  3. 所以当事件触发的时候, 对使用统一的分发函数 dispatchEvent 将指定函数执行

举个例子

export default class Test extends Component {
    constructor() {
        super(arguments);
        this.onReactClick.bind(this);
    }
    componentDidMount() {
        const parentDom = ReactDOM.findDOMNode(this);
        const childrenDom = parentDom.querySelector(".button");
        childrenDom.addEventListener('click', this.onDomClick, false);
    }
    onDomClick() {  // 事件委托
        console.log('Javascript Dom click');
    }
    onReactClick() {  // react合成事件
        console.log('React click');
    }
    render() {
        return (
            <div>
                <button className="button" onClick={this.onReactClick}>点击</button>
            </div>
        )
    }
}

会打印这个

Javascript Dom click
react-event.jsx:18 

如何在React中使用原生事件

虽然React封装了几乎所有的原生事件,但像:

  • Modal开启以后点击其他空白区域需要关闭Modal
  • 引入了一些以原生事件实现的第三方库,并且互相之间需要有交互

由于原生事件需要绑定在真实DOM上,所以一般是在componentDidMount阶段/ref的函数执行阶段进行绑定操作,在componentWillUnmount阶段进行解绑操作以避免内存泄漏。

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

如果业务场景中需要混用合成事件和原生事件,那使用过程中需要注意如下几点:

响应顺序

class Demo extends React.Component {
    componentDidMount() {
        const $this = ReactDOM.findDOMNode(this)
        $this.addEventListener('click', this.onDOMClick, false)
    }

    onDOMClick = evt => {
        console.log('dom event')
    }
    
    onClick = evt => {
        console.log('react event')
    }

    render() {
        return (
            <div onClick={this.onClick}>Demo</div>
        )
    }
}

我们来分析一下: 首先DOM事件监听器被执行,然后事件继续冒泡至document,合成事件监听器被执行。

addEventListener

note:
有点忘记addEventListener第三个参数是什么意思了。现在复习一下

事件冒泡或事件捕获

HTML DOM中有两种事件传播方式,即冒泡和捕获。 事件传播是一种在事件发生时定义元素顺序的方法。

  • 冒泡 : 即从里到外
  • 捕获 : 即从外到里

使用 addEventListener()方法,可以使用 useCapture 参数来指定传播类型, 默认值为false :

  • false : 使用冒泡传播
  • true : 使用捕获传播

阻止冒泡

那,如果在onDOMClick中调用evt.stopPropagation()呢?
由于DOM事件被阻止冒泡了,无法到达document,所以合成事件自然不会被触发

export default class Test extends Component {
    componentDidMount() {
        const $parent = ReactDOM.findDOMNode(this);
        const $child = $parent.querySelector('.child');
        $parent.addEventListener('click', this.onParentDOMClick, false)
        $child.addEventListener('click', this.onChildDOMClick, false)
    }

    onParentDOMClick = evt => {
        console.log('parent dom event')
    }

    onChildDOMClick = evt => {
        console.log('child dom event')
    }

    onParentClick = evt => {
        console.log('parent react event')
    }

    onChildClick = evt => {
        evt.stopPropagation();
        console.log('child react event')
    }

    render() {
        return (
            <div onClick={this.onParentClick}>
                <div className="child" onClick={this.onChildClick}>
                    Demo
                </div>
            </div>
        )
    }
}

在onChildClick中调用evt.stopPropagtion()
这样的结果是因为React给合成事件封装的stopPropagation函数调用时给自己加了isPropagationStopped的标记位来确定后续监听器是否执行。

总结

  1. 合成事件的监听器是统一注册在document上的,并且仅有冒泡阶段。所以原生事件的监听器响应总是比合成事件的监听器早
  2. 阻止原生事件的冒泡后,会阻止合成事件的监听器执行