我们知道在 react 应用中应该尽量使用 React 的事件,这样有更高效的性能,不容易内存泄露等等优点。但是在某些情况你不得不使用原生的事件,最常见的情况可能就是你希望点击 document 任意一处,去触发某个组件的 setState 使得其消失,比如 dropdown 、portal、popover 啊。当原生事件和 React 事件混合使用的时候,如何达到预想的效果,如何去阻止原生事件或者 React 事件的变得尤为重要。
首先我们知道在 React element 上绑定事件并不是在对应 dom 节点上绑定一个原生事件,而是当事件冒泡到 document, React 在 document
监听这个事件然后派发自己的 SyntheticEvent 冒泡通过整个 React component tree。如果你使用 Portal, React 甚至能智能的 route 事件冒泡的路径(而 DOM 事件只能沿着 DOM 节点往上冒泡 )
这里有两个关键,一个是绑定在 document 对象上,一个是通过原生事件的冒泡,所以我们可以:
- 在原生事件冒泡到
document上之前,我们可以通过原生事件的 e.stopPropagation() 阻止事件继续冒泡到document上,这样 React 就感受不到事件的发生,可以说我们阻断了 React 事件,请看下面:
<App onClick={onAppClick}>
<Button onClick={onButtonClick} />
</App>
// 事件应该先经过 body 然后到 document,所以我们在 body上按下面绑定,App 和 Button 上的 React 事件将不会触发
document.body.addEventListener('click', e => e.stopPropagation())
-
如果你希望先执行 React 事件的 handler,再执行你自己的原生事件的 handler,根据事件冒泡顺序,你可以在 document 或者 window对象上绑定你的 handler。这里有个前提,你绑定的 handler 必须在 React Tree mount 之后, 例如在 componentDidMount 之后绑定。
-
事件的传播经历三个阶段:捕捉、定位、冒泡。所以当你在捕捉阶段使用 stopPropagation() 阻止事件传播时,事件将不会经历冒泡,这种情况 React 也不会感知到任何事件,请看下面:
// 仅仅是为了解释所以用了 字符串形式的 ref
<App onClick={onAppClick} ref='app'>
<Button onClick={onButtonClick} />
</App>
// 伪代码:这么做 onAppClick 和 onButtonClick 也不会触发
this.app.addEventListener('click', e => e.stopPropagation(), true)
这里还有一个更细致的 demo:https://jsfiddle.net/fortesdotcom/n5u2wwjg/37487/
最后,如果你通过这个 demo 能预测到所有结果,那么恭喜你,对事件的机制可能有了更进一步的掌握,随手能写出属于你自己的 dropdown 组件等等。










网友评论