前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【React深入】React事件机制

【React深入】React事件机制

作者头像
ConardLi
发布2019-05-23 22:34:20
1.1K0
发布2019-05-23 22:34:20
举报
文章被收录于专栏:code秘密花园code秘密花园

关于React事件的疑问

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

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

事件注册

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

事件存储

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

例如:

代码语言:javascript
复制
{    onClick:{        nodeid1:()=>{...}        nodeid2:()=>{...}    },    onChange:{        nodeid3:()=>{...}        nodeid4:()=>{...}    }}

事件触发 / 执行

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

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

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

代码语言:javascript
复制
      <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自己实现的冒泡机制。

合成事件

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

总流程

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

为什么要手动绑定this

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

代码语言:javascript
复制
function invokeGuardedCallback(name, func, a) {  try {    func(a);  } catch (x) {    if (caughtError === null) {      caughtError = x;    }  }}

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

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

和原生事件有什么区别

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

例如, HTML

代码语言:javascript
复制
<button onclick="activateLasers()">  Activate Lasers</button>

React 中略有不同:

代码语言:javascript
复制
<button onClick={activateLasers}>  Activate Lasers</button>

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

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

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

代码语言:javascript
复制
  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事件都将无法被注册。

合成事件、浏览器兼容

代码语言:javascript
复制
  function handleClick(e) {    e.preventDefault();    console.log('The link was clicked.');  }

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

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

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

代码语言:javascript
复制
boolean bubblesboolean cancelableDOMEventTarget currentTargetboolean defaultPreventednumber eventPhaseboolean isTrustedDOMEvent nativeEventvoid preventDefault()boolean isDefaultPrevented()void stopPropagation()boolean isPropagationStopped()DOMEventTarget targetnumber timeStampstring type

React合成的 SyntheticEvent采用了事件池,这样做可以大大节省内存,而不会频繁的创建和销毁事件对象。

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-03-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 code秘密花园 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于React事件的疑问
  • 事件注册
  • 事件存储
  • 事件触发 / 执行
  • 合成事件
    • 总流程
    • 为什么要手动绑定this
    • 和原生事件有什么区别
    • React事件和原生事件的执行顺序
    • react事件和原生事件可以混用吗?
      • 合成事件、浏览器兼容
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档