文档章节

React 中阻止事件冒泡的问题

o
 osc_w9s1w4o0
发布于 2019/04/06 01:36
字数 4737
阅读 23
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

<table class="d-block"> <tbody class="d-block"> <tr class="d-block"> <td class="d-block comment-body markdown-body js-comment-body">

<p>在正式开始前,先来看看 JS 中事件的触发与事件处理器的执行。</p> <h2>JS 中事件的监听与处理</h2> <h3>事件捕获与冒泡</h3> <p>DOM 事件会先后经历 <strong>捕获</strong> 与 <strong>冒泡</strong> 两个阶段。捕获即事件沿着 DOM 树由上往下传递,到达触发事件的元素后,开始由下往上冒泡。</p> <blockquote> <p>IE9 及之前的版本只支持冒泡</p> </blockquote> <pre><code> | A -----------------|--|----------------- | Parent | | | | -------------|--|----------- | | |Children V | | | | ---------------------------- | | | -------------------------------------- </code></pre> <h3>事件处理器</h3> <p>默认情况下,事件处理器是在事件的冒泡阶段执行,无论是直接设置元素的 <code>onclick</code> 属性还是通过 <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener" rel="nofollow"><code>EventTarget.addEventListener()</code></a> 来绑定,后者在没有设置 <code>useCapture</code> 参数为 <code>true</code> 的情况下。</p> <p>考察下面的示例:</p> <div class="highlight highlight-text-html-basic"><pre>&lt;<span class="pl-ent">button</span> <span class="pl-e">onclick</span>=<span class="pl-s"><span class="pl-pds">"</span>btnClickHandler(event)<span class="pl-pds">"</span></span>&gt;CLICK ME&lt;/<span class="pl-ent">button</span>&gt; &lt;<span class="pl-ent">script</span>&gt;<span class="pl-s1"></span> <span class="pl-s1"> <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, <span class="pl-k">function</span>(<span class="pl-c1">event</span>) {</span> <span class="pl-s1"> <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document clicked<span class="pl-pds">"</span></span>);</span> <span class="pl-s1"> });</span> <span class="pl-s1"></span> <span class="pl-s1"> <span class="pl-k">function</span> <span class="pl-en">btnClickHandler</span>(<span class="pl-c1">event</span>) {</span> <span class="pl-s1"> <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>btn clicked<span class="pl-pds">"</span></span>);</span> <span class="pl-s1"> }</span> <span class="pl-s1"></span>&lt;/<span class="pl-ent">script</span>&gt;</pre></div> <p>输出:</p> <pre><code>btn clicked document clicked </code></pre> <h3>阻止事件的冒泡</h3> <p>通过调用事件身上的 <code>stopPropagation()</code> 可阻止事件冒泡,这样可实现只我们想要的元素处理该事件,而其他元素接收不到。</p> <div class="highlight highlight-text-html-basic"><pre>&lt;<span class="pl-ent">button</span> <span class="pl-e">onclick</span>=<span class="pl-s"><span class="pl-pds">"</span>btnClickHandler(event)<span class="pl-pds">"</span></span>&gt;CLICK ME&lt;/<span class="pl-ent">button</span>&gt; &lt;<span class="pl-ent">script</span>&gt;<span class="pl-s1"></span> <span class="pl-s1"> <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(</span> <span class="pl-s1"> <span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>,</span> <span class="pl-s1"> <span class="pl-k">function</span>(<span class="pl-c1">event</span>) {</span> <span class="pl-s1"> <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document clicked<span class="pl-pds">"</span></span>);</span> <span class="pl-s1"> },</span> <span class="pl-s1"> <span class="pl-c1">false</span></span> <span class="pl-s1"> );</span> <span class="pl-s1"></span> <span class="pl-s1"> <span class="pl-k">function</span> <span class="pl-en">btnClickHandler</span>(<span class="pl-c1">event</span>) {</span> <span class="pl-s1"> <span class="pl-c1">event</span>.<span class="pl-c1">stopPropagation</span>();</span> <span class="pl-s1"> <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>btn clicked<span class="pl-pds">"</span></span>);</span> <span class="pl-s1"> }</span> <span class="pl-s1"></span>&lt;/<span class="pl-ent">script</span>&gt;</pre></div> <p>输出:</p> <pre><code>btn clicked </code></pre> <h3>一个阻止冒泡的应用场景</h3> <p>常见的弹窗组件中,点击弹窗区域之外关闭弹窗的功能,可通过阻止事件冒泡来方便地实现,而不用这种方式的话,会引入复杂的判断当前点击坐标是否在弹窗之外的复杂逻辑。</p> <div class="highlight highlight-source-js"><pre><span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, () <span class="pl-k">=&gt;</span> { <span class="pl-c"><span class="pl-c">//</span> close dialog</span> });

<span class="pl-smi">dialogElement</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, <span class="pl-smi">event</span> <span class="pl-k">=></span> { <span class="pl-c1">event</span>.<span class="pl-c1">stopPropagation</span>(); });</pre></div>

<p>但如果你尝试在 React 中实现上面的逻辑,一开始的尝试会让你怀疑人生。</p> <h2>React 下事件执行的问题</h2> <p>了解 JS 中事件的基础后,会觉得一切都没什么复杂。但在引入 React 后,事情开始起变化。将上面阻止冒泡的逻辑在 React 里实现一下,代码大概像这样:</p> <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">App</span>() { <span class="pl-en">useEffect</span>(() <span class="pl-k">=&gt;</span> { <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentClickHandler); <span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> { <span class="pl-c1">document</span>.<span class="pl-c1">removeEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentClickHandler); }; }, []);

<span class="pl-k">function</span> <span class="pl-en">documentClickHandler</span>() { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document clicked<span class="pl-pds">"</span></span>); }

<span class="pl-k">function</span> <span class="pl-en">btnClickHandler</span>(<span class="pl-c1">event</span>) { <span class="pl-c1">event</span>.<span class="pl-c1">stopPropagation</span>(); <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>btn clicked<span class="pl-pds">"</span></span>); }

<span class="pl-k">return</span> <span class="pl-k"><</span>button onClick<span class="pl-k">=</span>{btnClickHandler}<span class="pl-k">></span><span class="pl-c1">CLICK</span> <span class="pl-c1">ME</span><span class="pl-k"><</span><span class="pl-k">/</span>button<span class="pl-k">></span>; }</pre></div>

<p>输出:</p> <pre><code>btn clicked document clicked </code></pre> <p>document 上的事件处理器正常执行了,并没有因为我们在按钮里面调用 <code>event.stopPropagation()</code> 而阻止。</p> <p>那么问题出在哪?</p> <h3>React 中事件处理的原理</h3> <p>考虑下面的示例代码并思考点击按钮后的输出。</p> <div class="highlight highlight-source-js"><pre><span class="pl-k">import</span> <span class="pl-smi">React</span>, { <span class="pl-smi">useEffect</span> } <span class="pl-k">from</span> <span class="pl-s"><span class="pl-pds">"</span>react<span class="pl-pds">"</span></span>; <span class="pl-k">import</span> <span class="pl-smi">ReactDOM</span> <span class="pl-k">from</span> <span class="pl-s"><span class="pl-pds">"</span>react-dom<span class="pl-pds">"</span></span>;

<span class="pl-c1">window</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, <span class="pl-smi">event</span> <span class="pl-k">=></span> { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>window<span class="pl-pds">"</span></span>); });

<span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, <span class="pl-smi">event</span> <span class="pl-k">=></span> { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document:bedore react mount<span class="pl-pds">"</span></span>); });

<span class="pl-c1">document</span>.<span class="pl-c1">body</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, <span class="pl-smi">event</span> <span class="pl-k">=></span> { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>body<span class="pl-pds">"</span></span>); });

<span class="pl-k">function</span> <span class="pl-en">App</span>() { <span class="pl-k">function</span> <span class="pl-en">documentHandler</span>() { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document within react<span class="pl-pds">"</span></span>); }

<span class="pl-en">useEffect</span>(() <span class="pl-k">=></span> { <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentHandler); <span class="pl-k">return</span> () <span class="pl-k">=></span> { <span class="pl-c1">document</span>.<span class="pl-c1">removeEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentHandler); }; }, []);

<span class="pl-k">return</span> ( <span class="pl-k"><</span>div onClick<span class="pl-k">=</span>{() <span class="pl-k">=></span> { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>raect:container<span class="pl-pds">"</span></span>); }} <span class="pl-k">></span> <span class="pl-k"><</span>button onClick<span class="pl-k">=</span>{<span class="pl-smi">event</span> <span class="pl-k">=></span> { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>react:button<span class="pl-pds">"</span></span>); }} <span class="pl-k">></span> <span class="pl-c1">CLICK</span> <span class="pl-c1">ME</span> <span class="pl-k"><</span><span class="pl-k">/</span>button<span class="pl-k">></span> <span class="pl-k"><</span><span class="pl-k">/</span>div<span class="pl-k">></span> ); }

<span class="pl-smi">ReactDOM</span>.<span class="pl-en">render</span>(<span class="pl-k"><</span>App <span class="pl-k">/</span><span class="pl-k">></span>, <span class="pl-c1">document</span>.<span class="pl-c1">getElementById</span>(<span class="pl-s"><span class="pl-pds">"</span>root<span class="pl-pds">"</span></span>));

<span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, <span class="pl-smi">event</span> <span class="pl-k">=></span> { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document:after react mount<span class="pl-pds">"</span></span>); });</pre></div>

<p>现在对代码做一些变动,在 body 的事件处理器中把冒泡阻止,再思考其输出。</p> <div class="highlight highlight-source-diff"><pre>document.body.addEventListener("click", event =&gt; { <span class="pl-mi1"><span class="pl-mi1">+</span> event.stopPropagation();</span> console.log("body"); });</pre></div> <p>下面是剧透环节,如果你懒得自己实验的话。</p> <p>点击按钮后的输出:</p> <pre><code>body document:bedore react mount react:button raect:container document:after react mount document within react window </code></pre> <p>bdoy 上阻止冒泡后,你可能会觉得,既然 body 是按钮及按钮容器的父级,那么按钮及容器的事件会正常执行,事件到达 body 后, body 的事件处理器执行,然后就结束了。 document 上的事件处理器一个也不执行。</p> <p>事实上,按钮及按钮容器上的事件处理器也没执行,只有 body 执行了。</p> <p>输出:</p> <pre><code>body </code></pre> <p>通过下面的分析,你能够完全理解上面的结果。</p> <h4>SyntheticEvent</h4> <p>React 有自身的一套事件系统,叫作 <a href="https://reactjs.org/docs/events.html" rel="nofollow">SyntheticEvent</a>。叫什么不重要,实现上,其实就是通过在 document 上注册事件代理了组件树中所有的事件(<a href="https://github.com/facebook/react/issues/4335#issuecomment-120269153" data-hovercard-type="issue" data-hovercard-url="/facebook/react/issues/4335/hovercard">facebook/react#4335</a>),并且它监听的是 document 冒泡阶段。你完全可以忽略掉 SyntheticEvent 这个名词,如果觉得它有点让事情变得高大上或者增加了一些神秘的话。</p> <p>除了事件系统,它有自身的一套,另外还需要理解的是,界面上展示的 DOM 与我们代码中的 DOM 组件,也是两样东西,需要在概念上区分开来。</p> <p>所以,当你在页面上点击按钮,事件开始在原生 DOM 上走捕获冒泡流程。React 监听的是 document 上的冒泡阶段。事件冒泡到 document 后,React 将事件再派发到组件树中,然后事件开始在组件树 DOM 中走捕获冒泡流程。</p> <p>现在来尝试理解一下输出结果:</p> <ul> <li>事件最开始从原生 DOM 按钮一路冒泡到 body,body 的事件处理器执行,输出 <code>body</code>。注意此时流程还没进入 React。为什么?因为 React 监听的是 document 上的事件。</li> <li>继续往上事件冒泡到 document。 <ul> <li>事件到达 document 之后,发现 document 上面一共绑定了三个事件处理器,分别是代码中通过 <code>document.addEventListener</code> 在 <code>ReactDOM.render</code> 前后调用的,以及一个隐藏的事件处理器,是 <a href="https://github.com/facebook/react/blob/e8857918422b5ce8505ba5ce4a2d153e509c17a1/packages/react-dom/src/events/ReactBrowserEventEmitter.js#L105-L173">ReactDOM 绑定的</a>,也就是前面提到的 React 用来代理事件的那个处理器。</li> <li>同一元素上如果对同一类型的事件绑定了多个处理器,会按照绑定的顺序来执行。</li> <li>所以 <code>ReactDOM.render</code> 之前的那个处理器先执行,输出 <code>document:before react mount</code>。</li> <li>然后是 React 的事件处理器。此时,流程才真正进入 React,走进我们的组件。组件里面就好理解了,从 button 冒泡到 container,依次输出。</li> <li>最后 <code>ReactDOM.render</code> 之后的那个处理器先执行,输出 <code>document:after react mount</code>。</li> </ul> </li> <li>事件完成了在 document 上的冒泡,往上到了 window,执行相应的处理器并输出 <code>window</code>。</li> </ul> <p>理解 <strong>React 是通过监听 document 冒泡阶段来代理组件中的事件</strong>,这点很重要。同时,区分原生 DOM 与 React 组件,也很重要。并且,React 组件上的事件处理器接收到的 <code>event</code> 对象也有别于原生的事件对象,不是同一个东西。但这个对象上有个 <code>nativeEvent</code> 属性,可获取到原生的事件对象,后面会用到和讨论它。</p> <p>紧接着的代码的改动中,我们在 body 上阻止了事件冒泡,这样事件在 body 就结束了,没有到达 document,那么 React 的事件就不会被触发,所以 React 组件树中,按钮及容器就没什么反应。如果没理解到这点,光看表象还以为是 bug。</p> <p>进而可以理解,如果在 <code>ReactDOM.render()</code> 之前的的 document 事件处理器上将冒泡结束掉,同样会影响 React 的执行。只不过这里需要调用的不是 <code>event.stopPropagation()</code>,而是 <code>event.stopImmediatePropagation()</code>。</p> <div class="highlight highlight-source-diff"><pre>document.addEventListener("click", event =&gt; { <span class="pl-mi1"><span class="pl-mi1">+</span> event.stopImmediatePropagation();</span> console.log("document:bedore react mount"); });</pre></div> <p>输出:</p> <pre><code>body document:bedore react mount </code></pre> <p><code>stopImmediatePropagation</code> 会产生这样的效果,即,如果同一元素上同一类型的事件(这里是 <code>click</code>)绑定了多个事件处理器,本来这些处理器会按绑定的先后来执行,但如果其中一个调用了 <code>stopImmediatePropagation</code>,不但会阻止事件冒泡,还会阻止这个元素后续其他事件处理器的执行。</p> <p>所以,虽然都是监听 document 上的点击事件,但 <code>ReactDOM.render()</code> 之前的这个处理器要先于 React,所以 React 对 document 的监听不会触发。</p> <h3>解答前面按钮未能阻止冒泡的问题</h3> <details> <summary>如果你已经忘了,这是相应的代码及输出。</summary> <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">App</span>() { <span class="pl-en">useEffect</span>(() <span class="pl-k">=&gt;</span> { <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentClickHandler); <span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> { <span class="pl-c1">document</span>.<span class="pl-c1">removeEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentClickHandler); }; }, []);

<span class="pl-k">function</span> <span class="pl-en">documentClickHandler</span>() { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document clicked<span class="pl-pds">"</span></span>); }

<span class="pl-k">function</span> <span class="pl-en">btnClickHandler</span>(<span class="pl-c1">event</span>) { <span class="pl-c1">event</span>.<span class="pl-c1">stopPropagation</span>(); <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>btn clicked<span class="pl-pds">"</span></span>); }

<span class="pl-k">return</span> <span class="pl-k"><</span>button onClick<span class="pl-k">=</span>{btnClickHandler}<span class="pl-k">></span><span class="pl-c1">CLICK</span> <span class="pl-c1">ME</span><span class="pl-k"><</span><span class="pl-k">/</span>button<span class="pl-k">></span>; }</pre></div>

<p>输出:</p> <pre><code>btn clicked document clicked </code></pre> </details> <p>到这里,已经可以解答为什么 React 组件中 button 的事件处理器中调用 <code>event.stopPropagation()</code> 没有阻止 document 的点击事件执行的问题了。因为 button 事件处理器的执行前提是事件达到 document 被 React 接收到,然后 React 将事件派发到 button 组件。既然在按钮的事件处理器执行之前,事件已经达到 document 了,那当然就无法在按钮的事件处理器进行阻止了。</p> <h2>问题的解决</h2> <p>要解决这个问题,这里有不止一种方法。</p> <h3>用 <code>window</code> 替换 <code>document</code></h3> <p>来自 <a href="https://github.com/facebook/react/issues/4335#issuecomment-421705171" data-hovercard-type="issue" data-hovercard-url="/facebook/react/issues/4335/hovercard">React issue 回答</a>中提供的这个方法是最快速有效的。使用 window 替换掉 document 后,前面的代码可按期望的方式执行。</p> <div class="highlight highlight-source-diff"><pre>function App() { useEffect(() =&gt; { <span class="pl-mi1"><span class="pl-mi1">+</span> window.addEventListener("click", documentClickHandler);</span> return () =&gt; { <span class="pl-mi1"><span class="pl-mi1">+</span> window.removeEventListener("click", documentClickHandler);</span> }; }, []);

function documentClickHandler() { console.log("document clicked"); }

function btnClickHandler(event) { event.stopPropagation(); console.log("btn clicked"); }

return <button onClick={btnClickHandler}>CLICK ME</button>; }</pre></div>

<p>这里 button 事件处理器上接到到的 event 来自 React 系统,也就是 document 上代理过来的,所以通过它阻止冒泡后,事件到 document 就结束了,而不会往上到 window。</p> <h3><code>Event.stopImmediatePropagation()</code></h3> <p>组件中事件处理器接收到的 <code>event</code> 事件对象是 React 包装后的 SyntheticEvent 事件对象。但可通过它的 <code>nativeEvent</code> 属性获取到原生的 DOM 事件对象。通过调用这个原生的事件对象上的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation" rel="nofollow"><code>stopImmediatePropagation()</code></a> 方法可达到阻止冒泡的目的。</p> <div class="highlight highlight-source-diff"><pre>function btnClickHandler(event) { <span class="pl-mi1"><span class="pl-mi1">+</span> event.nativeEvent.stopImmediatePropagation();</span> console.log("btn clicked"); }</pre></div> <p>至于原理,其实前面已经有展示过。React 在 render 时监听了 document 冒泡阶段的事件,当我们的 <code>App</code> 组件执行时,准确地说是渲染完成后(<code>useEffect</code> 渲染完成后执行),又在 document 上注册了 click 的监听。此时 document 上有两个事件处理器了,并且组件中的这个顺序在 React 后面。</p> <p>当调用 <code>event.nativeEvent.stopImmediatePropagation()</code> 后,阻止了 document 上同类型后续事件处理器的执行,达到了想要的效果。</p> <p>但这种方式有个缺点很明显,那就是要求需要被阻止的事件是在 React render 之后绑定,如果在之前绑定,是达不到效果的。</p> <h3>通过元素自身来绑定事件处理器</h3> <p>当绕开 React 直接通过调用元素自己身上的方法来绑定事件时,此时走的是原生 DOM 的流程,都没在 React 的流程里面。</p> <div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> <span class="pl-en">App</span>() { <span class="pl-k">const</span> <span class="pl-c1">btnElement</span> <span class="pl-k">=</span> <span class="pl-en">useRef</span>(<span class="pl-c1">null</span>); <span class="pl-en">useEffect</span>(() <span class="pl-k">=&gt;</span> { <span class="pl-c1">document</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentClickHandler); <span class="pl-k">if</span> (<span class="pl-smi">btnElement</span>.<span class="pl-c1">current</span>) { <span class="pl-smi">btnElement</span>.<span class="pl-c1">current</span>.<span class="pl-c1">addEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, btnClickHandler); }

<span class="pl-k">return</span> () <span class="pl-k">=&gt;</span> {
  <span class="pl-c1">document</span>.<span class="pl-c1">removeEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, documentClickHandler);
  <span class="pl-k">if</span> (<span class="pl-smi">btnElement</span>.<span class="pl-c1">current</span>) {
    <span class="pl-smi">btnElement</span>.<span class="pl-c1">current</span>.<span class="pl-c1">removeEventListener</span>(<span class="pl-s"><span class="pl-pds">"</span>click<span class="pl-pds">"</span></span>, btnClickHandler);
  }
};

}, []);

<span class="pl-k">function</span> <span class="pl-en">documentClickHandler</span>() { <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>document clicked<span class="pl-pds">"</span></span>); }

<span class="pl-k">function</span> <span class="pl-en">btnClickHandler</span>(<span class="pl-c1">event</span>) { <span class="pl-c1">event</span>.<span class="pl-c1">stopPropagation</span>(); <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>btn clicked<span class="pl-pds">"</span></span>); }

<span class="pl-k">return</span> <span class="pl-k"><</span>button ref<span class="pl-k">=</span>{btnElement}<span class="pl-k">></span><span class="pl-c1">CLICK</span> <span class="pl-c1">ME</span><span class="pl-k"><</span><span class="pl-k">/</span>button<span class="pl-k">></span>; }</pre></div>

<p>很明显这样是能解决问题,但你根本不会想要这样做。代码丑陋,不直观也不易理解。</p> <h2>结论</h2> <p>注意区分 React 组件的事件及原生 DOM 事件,一般情况下,尽量使用 React 的事件而不要混用。如果必需要混用比如监听 document,window 上的事件,处理 <code>mousemove</code>,<code>resize</code> 等这些场景,那么就需要注意本文提到的顺序问题,不然容易出 bug。</p> <h2>相关资源</h2> <ul> <li><a href="https://github.com/facebook/react/issues/4335" data-hovercard-type="issue" data-hovercard-url="/facebook/react/issues/4335/hovercard">e.stopPropagation() seems to not be working as expect. #4335</a></li> <li><a href="https://stackoverflow.com/questions/24415631/reactjs-syntheticevent-stoppropagation-only-works-with-react-events" rel="nofollow">ReactJS SyntheticEvent stopPropagation() only works with React events?</a></li> <li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/stopImmediatePropagation" rel="nofollow">Event.stopImmediatePropagation()</a></li> <li><a href="https://reactjs.org/docs/events.html#supported-events" rel="nofollow">SyntheticEvent</a></li> </ul> </td> </tr> </tbody> </table>

o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
Netty那点事(三)Channel与Pipeline

Channel是理解和使用Netty的核心。Channel的涉及内容较多,这里我使用由浅入深的介绍方法。在这篇文章中,我们主要介绍Channel部分中Pipeline实现机制。为了避免枯燥,借用一下《盗梦空间》的...

黄亿华
2013/11/24
2W
22
程序猿媛一:Android滑动翻页+区域点击事件

滑动翻页+区域点击事件 ViewPager+GrideView 声明:博文为原创,文章内容为,效果展示,思路阐述,及代码片段。文尾附注源码获取途径。 转载请保留原文出处“http://my.oschina.net/gluoyer...

花佟林雨月
2013/11/09
4.3K
1
Flash 皮肤样式--Windows8UIStyle

Windows8UIStyle 模仿 Windows 8 的桌面用户界面,使得 FlashSwing 应用程序在 Windows 8 系统中拥有与传统应用程序一致的用户界面。 Windows8UIStyle 对 FlashSwing 默认主题的修改: 提供和...

Gregary
2013/02/19
1.3K
1
服务器自动化任务解决方案--Huginn

Huginn 是雅虎开发的一个系统,可以帮你执行自动化的在线任务。可以阅读网页,关注事件,并采取相应操作。Huginn 通过一个直观的事件流图来展示各种操作和事件。通过在你自己的服务器上的管道加...

匿名
2013/03/15
1.7W
0
浏览器中的scheme解释器--SchemeScript

一个用javascript实现的scheme解释器,可以运行在浏览器中或node.js中。 刚刚看到编译原理与实践第二章,一时兴起,想写个以前就想写的scheme的解释器。昨天晚上开始写,到刚才为止,接近一天...

zoowii
2012/11/01
1.2K
0

没有更多内容

加载失败,请刷新页面

加载更多

程序员职场:拥有一个学位将会在你的职业生涯中更加顺利!

1、作为程序员为什么要拥有学位? 很多情况下,作为程序员,学位是进入大公司的敲门砖。 现在很多大的科技公司,学位是硬性要求。 一般都是本科以上的学历,甚至有的必须是硕士以上学历。 如...

IT技术分享社区
03/03
12
0
varchar和nvarchar有什么区别? - What is the difference between varchar and nvarchar?

问题: Is it just that nvarchar supports multibyte characters? 只是nvarchar支持多字节字符吗? If that is the case, is there really any point, other than storage concerns, to us......

技术盛宴
39分钟前
5
0
用flutter给图片加个好看的遮罩层【flutter20个实例之六】

一、老套路,先看样式 左起图一是我业务中的样式,左起图二、三是下方源码展示样式(复制可直接运行,无额外组件引入) 二、讲解 1.结构拆分 我们先看下页面布局结构,首先肯定是有个GridVie...

一代码农码一代
40分钟前
17
0
世界上最美的瀑布在这里,太美了!

亲近大自然,高山流水遇知音,倾听心灵的声音。。。 声明:文章及图片、视频来自网络,如有版权方面的疑问请和我们联系,我们将在24小时内删除。 本文分享自微信公众号 - Python提升课堂(DJXY0...

花儿开放
2014/08/17
0
0
商城小程序制作流程

随着商城小程序的火爆,很多商家都迫不及待的想制作商城小程序,下面就和大家分享一下商城小程序制作流程? 第1步: 注册并认证小程序账号 注册并认证小程序账号,打开百度搜索,“微信公众平...

木鱼小铺小程序1
50分钟前
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部