前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >react是如何实现冒泡的

react是如何实现冒泡的

作者头像
IMWeb前端团队
发布2019-12-03 17:07:12
1.7K0
发布2019-12-03 17:07:12
举报
文章被收录于专栏:IMWeb前端团队IMWeb前端团队

本文作者:IMWeb jerytang 原文出处:IMWeb社区 未经同意,禁止转载

react 自己实现了一套事件冒泡机制,将所有事件都用代理的方式绑定到 document上。这里谈下我对 react 的冒泡实现的理解,不对的请指出。

两种事件模型

我们知道,在标准里面是支持 bubble 和 capture 两种事件模型的。

React 也支持这两种事件模型,很大可能你还没有使用过 React 的事件捕获,看下面的例子:

使用事件冒泡,如果点击按钮,childOnclick 会被触发,然后 parentOnclick 会被触发,如果 childOnClick 中调用了 event.stopPropagation(),阻止了冒泡,那么 parentOnClick 就不会触发了。这个过程是 child 到 parent,是自底向上的,就像冒泡一样。

代码语言:javascript
复制
<div onClick={this.parentOnClick}>
  <button onClick={this.childOnClick}>冒泡的事件!</button>
</div>

像web标准一样,其实也可以反过来,先是父级组件先触发事件,然后再一级往下传递,这种方式被称为捕获。使用 onEventNameCapture,就是使用捕获的方式,下面的代码会先执行 parentOnClick,再执行 childOnClick,如果在 parentOnClick 调用了 stopPropagation 阻止事件传递,那么就会导致 childOnClick 不会被触发。

代码语言:javascript
复制
<div onClickCapture={this.parentOnClick}>
  <button onClick={this.childOnClick}>捕获的事件!</button>
</div>

为什么

为什么会有这两种事件模型呢?

一方面从历史沿革来看,在浏览器的早期,Netscape 浏览器是使用的 capture 事件模型,而 IE 使用的是冒泡模型,后来的标准里面就有了这两种模型可选:

代码语言:javascript
复制
element.addEventListner(name, fn, useCapture)

useCapture 为 true 表示使用捕获,useCapture 为 false 表示使用冒泡。

现在,大家从使用习惯上来讲,使用冒泡会比较多。addEventListner 的第 3 个参数 useCapture 的默认值也是 false.

另一方面,从性能上来讲,捕获模型的性能会好一丢丢,见 这里的讨论.

react/类react框架是如何实现冒泡机制的?

前面是铺垫,现在引入主题。

有一个问题一直困惑我:有些事件是不支持事件冒泡的,比如 blur 事件,那么 react 是如何实现这类事件冒泡的?

代码语言:javascript
复制
<div id="el">
  <input type="text" id="input">
</div>

如果使用原生的方式,在 el 绑定 blur 事件,在 input 上也绑定 blur 事件,当 input 触发 blur 事件,其父元素并不会触发 blur 事件。下面的代码,只会输出 #2.

代码语言:javascript
复制
const el = document.querySelector('#el');
const ip = document.querySelector('#input');

el.addEventListener('blur', function(e) {
  console.log(`#1 new ${e.target.value}`)
}, false)

ip.addEventListener('blur', function(e) {
  console.log(`#2 ${e.target.value}`)
})

而在 react 中,当 input blur 事件触发后,会按照 #1 #2 的顺序输出

代码语言:javascript
复制
<div onBlur={this.parentOnBlur}>
  <input type="text" onBlur={this.childOnBlur}>
</div>

如果你使用的是一些类 react 的方案,比如 react-lite,可能会存在bug的,上面的代码,在 react-lite 不能按照预期的方式冒泡。

实现方案一

在 ninjia javascript这本书中,有对不能冒泡的特殊事件进行处理,以 change 事件为例,总结来讲就是

  • 实现一个 triggerEvent 方法,能手动触发事件
  • 如果目标元素不支持冒泡,那么使用其他的事件来监测子元素的 change 变化
  • 分别绑定 focusout click keydown beforeactivate 等监控函数
  • 当发现目标元素,比如 input,发生了值的变化,那么调用 triggerEvent
  • triggerEvent 会被递归的,冒泡调用
  • 如此,实现了冒泡不能冒泡的事件
  • 具体实现参见 Secrets of the JavaScript Ninja 这本书的 13.5 bubbling and delegation
pic1
pic1

实现方案二

anu.js 的作者在 blog中写道

对于focus,blur,change,submit,reset,select等不会冒泡的事件,在标准游览器中,我们可以设置addEventListener的最后一个参数为true轻松搞定

  • 巧妙的使用 addEventListener 的第3个捕获参数,那么首先事件就会在 root 被捕获
  • 然后获取到 e.target 也就是 input元素,然后再通过 input 元素,往上触发事件,实现冒泡
代码语言:javascript
复制
// 使用 capture 参数来实现捕获不能冒泡的事件
const el = document.querySelector('#el');
const ip = document.querySelector('#input');

el.addEventListener('blur', function(e) {
console.log(`#1 new ${e.target.value}`)
}, true); // blur 事件触发,将先打出 #1,再打出 #2

ip.addEventListener('blur', function(e) {
console.log(`#2 ${e.target.value}`)
})

比如在兼容 react 的框架 anu.js 中,对不能冒泡的 blur 事件是这样处理的:

pic2
pic2
  • react 事件是绑定到 document上的,所以 e.currentTarget 是 document,e.target 是 input
  • 根据 input,获取向上冒泡的路径,即会冒泡元素 collectPaths,然后一个循环触发,如果循环中有 stopPropagation,那么终止循环

当然这都不是 react 的实际实现,因为 React 的代码太难读了,盘根错节,我还没有找到具体实现在哪里。如有理解不正确,欢迎指出 ^_^。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-08-06 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 两种事件模型
  • 为什么
  • react/类react框架是如何实现冒泡机制的?
    • 实现方案一
      • 实现方案二
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档