【React深入】React事件机制

关于React事件的疑问

  • 1.为什么要手动绑定 this
  • 2. React事件和原生事件有什么区别
  • 3. React事件和原生事件的执行顺序,可以混用吗
  • 4. React事件如何解决跨浏览器兼容
  • 5.什么是合成事件

下面是我阅读过源码后,将所有的执行流程总结出来的流程图,不会贴代码,如果你想阅读代码看看具体是如何实现的,可以根据流程图去源码里寻找。

事件注册

  • 组件装载 / 更新。
  • 通过 lastProps、 nextProps判断是否新增、删除事件分别调用事件注册、卸载方法。
  • 调用 EventPluginHub的 enqueuePutListener进行事件存储
  • 获取 document对象。
  • 根据事件名称(如 onClick、 onCaptureClick)判断是进行冒泡还是捕获。
  • 判断是否存在 addEventListener方法,否则使用 attachEvent(兼容IE)。
  • 给 document注册原生事件回调为 dispatchEvent(统一的事件分发机制)。

事件存储

  • EventPluginHub负责管理React合成事件的 callback,它将 callback存储在 listenerBank中,另外还存储了负责合成事件的 Plugin
  • EventPluginHub的 putListener方法是向存储容器中增加一个listener。
  • 获取绑定事件的元素的唯一标识 key
  • 将 callback根据事件类型,元素的唯一标识 key存储在 listenerBank中。
  • listenerBank的结构是: listenerBank[registrationName][key]

例如:

{
onClick:{
    nodeid1:()=>{...}
    nodeid2:()=>{...}
},
onChange:{
    nodeid3:()=>{...}
    nodeid4:()=>{...}
}
}

事件触发 / 执行

这里的事件执行利用了 React的批处理机制,在前一篇的【React深入】setState执行机制中已经分析过,这里不再多加分析。

  • 触发 document注册原生事件的回调 dispatchEvent
  • 获取到触发这个事件最深一级的元素

例如下面的代码:首先会获取到 this.child

<div onClick={this.parentClick} ref={ref => this.parent = ref}>
    <div onClick={this.childClick} ref={ref => this.child = ref}>
        test
    </div>
</div>
  • 遍历这个元素的所有父元素,依次对每一级元素进行处理。
  • 构造合成事件。
  • 将每一级的合成事件存储在 eventQueue事件队列中。
  • 遍历 eventQueue
  • 通过 isPropagationStopped判断当前事件是否执行了阻止冒泡方法。
  • 如果阻止了冒泡,停止遍历,否则通过 executeDispatch执行合成事件。
  • 释放处理完成的事件。

react在自己的合成事件中重写了 stopPropagation方法,将 isPropagationStopped设置为 true,然后在遍历每一级事件的过程中根据此遍历判断是否继续执行。这就是 react自己实现的冒泡机制。

合成事件

  • 调用 EventPluginHub的 extractEvents方法。
  • 循环所有类型的 EventPlugin(用来处理不同事件的工具方法)。
  • 在每个 EventPlugin中根据不同的事件类型,返回不同的事件池。
  • 在事件池中取出合成事件,如果事件池是空的,那么创建一个新的。
  • 根据元素 nodeid(唯一标识 key)和事件类型从 listenerBink中取出回调函数
  • 返回带有合成事件参数的回调函数

总流程

将上面的四个流程串联起来。

为什么要手动绑定this

通过事件触发过程的分析, dispatchEvent调用了 invokeGuardedCallback方法。

function invokeGuardedCallback(name, func, a) {  
    try {
        func(a);
    } catch (x) {
        if (caughtError === null) {
            caughtError = x;
        }
    }
}

可见,回调函数是直接调用调用的,并没有指定调用的组件,所以不进行手动绑定的情况下直接获取到的 thisundefined

这里可以使用实验性的属性初始化语法 ,也就是直接在组件声明箭头函数。箭头函数不会创建自己的 this,它只会从自己的作用域链的上一层继承 this。因此这样我们在 React事件中获取到的就是组件本身了。

和原生事件有什么区别

  • React 事件使用驼峰命名,而不是全部小写。
  • 通过 JSX , 你传递一个函数作为事件处理程序,而不是一个字符串。

例如, HTML

<button onclick="activateLasers()">
Activate Lasers
</button>

React 中略有不同:

<button onClick={activateLasers}>
Activate Lasers
</button>

另一个区别是,在 React 中你不能通过返回 false 来阻止默认行为。必须明确调用 preventDefault

由上面执行机制我们可以得出: React自己实现了一套事件机制,自己模拟了事件冒泡和捕获的过程,采用了事件代理,批量更新等方法,并且抹平了各个浏览器的兼容性问题。

React事件和原生事件的执行顺序

componentDidMount() {
    this.parent.addEventListener('click', (e) => {
        console.log('dom parent');
     })
    this.child.addEventListener('click', (e) => {
        console.log('dom child');
    })
    document.addEventListener('click', (e) => {
        console.log('document');
    })
}

childClick = (e) => {
    console.log('react child');
}

parentClick = (e) => {
    console.log('react parent');
}

render() {
    return (
        <div onClick={this.parentClick} ref={ref => this.parent = ref}>
        <div onClick={this.childClick} ref={ref => this.child = ref}>
            test
        </div>
    </div>)
}

执行结果:

由上面的流程我们可以理解:

  • react的所有事件都挂载在 document

  • 当真实dom触发后冒泡到 document后才会对 react事件进行处理

  • 所以原生的事件会先执行

  • 然后执行 react合成事件

  • 最后执行真正在 document上挂载的事件

react事件和原生事件可以混用吗?

react事件和原生事件最好不要混用。

原生事件中如果执行了 stopPropagation方法,则会导致其他 react事件失效。因为所有元素的事件将无法冒泡到 document上。

由上面的执行机制不难得出,所有的react事件都将无法被注册。

合成事件、浏览器兼容

function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
}

这里, e 是一个合成的事件。 React 根据 W3C 规范 定义了这个合成事件,所以你不需要担心跨浏览器的兼容性问题。

事件处理程序将传递 SyntheticEvent 的实例,这是一个跨浏览器原生事件包装器。 它具有与浏览器原生事件相同的接口,包括 stopPropagation()preventDefault() ,在所有浏览器中他们工作方式都相同。

每个 SyntheticEvent对象都具有以下属性:

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合成的 SyntheticEvent采用了事件池,这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。

另外,不管在什么浏览器环境下,浏览器会将该事件类型统一创建为合成事件,从而达到了浏览器兼容的目的

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值