-
Notifications
You must be signed in to change notification settings - Fork 383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
React 事件代理与 stopImmediatePropagation #107
Comments
谢谢作者~ |
1 similar comment
谢谢作者~ |
写的太好了!! |
这种情况,除非你全部使用 React 绑定或者原生绑定,否则我想不到可阻止的方法。 @Lx15 |
@youngwind 或者 在父元素上用target判断一下,可行不优雅 |
哈哈哈,角度刁钻,但也确实可行。 @Lx15 |
@youngwind 作者你好,我又认真的读了你的文章,做了测试,发现是有问题的:
|
大赞!!! |
结论: |
感谢作者,同类分析里写的简单易懂的一个 |
写得不错 |
测试发现document原生绑定事件在合成的事件执行之后执行,原生事件(document元素事件)-》点击子元素-》点击父元素-》直接绑定在document上的事件。链接地址 |
补充一下在react页面里面打印了一下绑定在document上的click事件,发现第一个就是react绑定的事件,然后才是我在Didmount上绑定的事件 |
根据我的测试结果,和你的结论有点出入,一起讨论下:
你说的这种情况只在给 非document,window元素 ` 添加原生事件时成立,当给document添加原生事件时,还取决于添加原生事件的时机: 在render之前添加原生事件,那么原生事件先于合成事件绑定,触发时先执行原生事件。
因为在作者在
我测试的结果是,这个api对其它元素也有效。可能我理解错你的意思了? |
React有自己的事件机制,React中的合成事件是通过事件委托委托给document元素,React会在document元素上绑定一个事件函数,React中所有的事件绑定都通过事件委托委托在document元素上,所以当React的合成事件触发时,其实会调用document绑定的事件函数,所以当在子元素的合成事件中调用e.stopPropagation()时是没办法组织原生事件的。再者,React中合成事件的e.stopPropagation()是自己定义的一个函数,和原生事件的stopPropagation没有任何联系,所以自然无法阻止原生事件的冒泡。
刚刚说了,React所有的事件都是委托在document元素上的,所以仔细看一下输出,会发现parent的原生事件回调函数比react合成事件的回调函数先执行
…------------------ 原始邮件 ------------------
发件人: "wujiedong"<notifications@github.com>;
发送时间: 2019年6月19日(星期三) 下午3:03
收件人: "youngwind/blog"<blog@noreply.github.com>;
抄送: "Subscribed"<subscribed@noreply.github.com>;
主题: Re: [youngwind/blog] React 事件代理与 stopImmediatePropagation (#107)
作者你好,很感谢你的文章,解开了我的疑惑,但是如果是下面的这种情景该怎样解决呢?
我想在点击子元素时,只是点击子元素,而不点击父元素,该怎样阻止呢?
parent的那个事件使用react中的合成事件,不要写原生事件,然后再子dom的事件中直接e.stopPropagation(),这样事件就不会冒泡到父那边
我试过子是合成事件,父是原生事件,这种方式我没有成功阻止冒泡
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub, or mute the thread.
|
非常非常非常有用,感谢!!! |
写的很清楚。但是有个地方有问题: |
太赞了!!! |
前言
我们都知道:“React 组件绑定事件本质上是代理到 document 上”,今天,我们来探索其精微之处。
stopPropagation VS stopImmediatePropagation
考虑这么一种情况:document 上绑定了 3 个事件,有什么办法能够做到 → 在触发了第 1 个事件之后,不再触发第 2、3 个事件呢?我的第一想法是调用
e.stopPropagation
。但是,实际验证是不行的,如下图所示。为什么呢?我重新翻开红宝书,第 356 页,发现除了
e.stopPropagation
外,还有一个类似的方法,叫做e.stopImmediatePropagation
,它们两个的区别是:e.stopImmediatePropagation
。更多关于 stopImmediatePropagation 的定义,可自行参考 MDN。
附上面例子的事件流图解
React 事件代理
虽然很多资料都说 React 的事件是会被代理到 document 上,但是我翻遍了官网,也没有找到相应的说明。那么,有什么办法能够证明它吗?我想到了一个方法 → 通过 Chrome 浏览器的 Event Listeners 面板查看元素的绑定事件,具体的使用方法请参考官网文档。
从图中我们可以看到:
React 模拟 DOM 事件冒泡机制
观察下面这个例子:#child 和 #parent 都绑定了一个点击事件。
由图中可以看出:点击 #child 的同时,也触发了 #parent 的点击事件,看起来“很像” DOM 的事件冒泡机制。然而,实际原理并非如此,因为按照 React 的事件代理,#child 和 #parent 绑定的事件本来就是代理到 document 上的。也就是说,只有当事件流冒泡到 document 上时,才会依次触发 document 上绑定的两个事件。
到此为止,我以为我终于搞明白这块了,后来我发现我还是错了。如果说 #child 和 #parent 的事件都代理到 document 上的话,那么在 Event Listeners 面板中,我们应该能看到 2 个绑定在 document 上的事件,但实际上只有 1 个,如下图所示。
因此,我们可以得出结论:并非 #child 和 #parent 的事件分别代理到 document 上,而是 React 在 document 上绑定了一个 dispatchEvent 函数(事件),在执行 dispatchEvent 的过程中,其内部会依次执行 #child 和 #parent 上绑定的事件。请注意,虽然 dispatchEvent 和代理到 document 上这两种方式的表现结果一样,但是其本质是有很大差别的,后边我们结合到 stopImmediatePropagation 的时候便会讲到。
那么这个 dispatchEvent 函数又是如何做到依次触发 #child 和 #parent 的事件的呢?我无力研究 React 这部分的源码,只好自己猜想了一下,其伪代码可能是这样子:
这应该便是 React 模拟 DOM 事件冒泡的大致原理。
React 禁止事件冒泡
既然有“事件冒泡”,就得有相应的禁止它的方法,这一点 React 的官网中便有提到:通过 React 绑定的事件,其回调函数中的 event 对象,是经过 React 合成的 SyntheticEvent,与原生的 DOM 事件的 event 不是一回事。准确地说,在 React 中,e.nativeEvent 才是原生 DOM 事件的那个 event,虽然 React 的合成事件对象也同样实现了 stopPropagation 接口。
因此,在 React 中,想要阻止“事件冒泡”(再强调一次,React 只是模拟事件冒泡,并非真正的 DOM 事件冒泡),只需要在回调函数中调用
e.stopPropagation
。请注意,这时候的e.stopPropagation
非原生事件对象的 stopPropagation。以上这些都是官网中已经有的,那本文又有什么新意呢?请看下面的例子:#child、#parent 和 document 上都绑定了事件,如何做到只触发 #child 上的事件?
我们来尝试解释一下上图中的现象:
e.stopPropagation
,成功地阻止了 React 模拟的事件冒泡,因此,成功地没有触发 #parent 上的事件。e.nativeEvent.stopImmediatePropagation
“上述过程用图解的方式来分析,我们能理解得清楚一些。
React 合成事件对象的
e.stopPropagation
,只能阻止 React 模拟的事件冒泡,并不能阻止真实的 DOM 事件冒泡,更加不能阻止已经触发元素的多个事件的依次执行。在这种情况下,只有原生事件对象的stopImmediatePropagation
能做到。你可能会说:”既然 React 在合成事件对象中封装了 stopPropagation,为什么不把
stopImmediatePropagation 也一并封装了呢?“
我的猜测是:”因为在 React 中不允许给同一个组件绑定多个相同类型的事件,如果非要重复绑定,那么后绑定的会覆盖前绑定的,这是它的设计思路。在这种设计思路下,不会存在某个组件有多个同类型的事件会依次触发,自然便不需要 stopImmediatePropagation 了。
总结
对于 React 的合成事件对象 e 来说:
最后,本文对应的 demo 请参考这里:https://jsfiddle.net/youngwind/91es1dbx/5/
很久以前我也写过一篇关于此主题的博客 #9 ,不过现在看来,那时候的思考很不成熟,也一并列在这儿以作参考吧。
The text was updated successfully, but these errors were encountered: