sos*_*ick 6 css reactjs react-redux
我们有菜单。如果菜单是打开的,我们应该可以通过单击任意位置来关闭它:
class Menu extends Component {
componentWillMount() {
document.addEventListener("click", this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener("click", this.handleClickOutside);
}
openModal = () => {
this.props.showModal();
};
handleClickOutside = ({ target }) => {
const { displayMenu, toggleMenu, displayModal } = this.props;
if (displayMenu) {
if (displayModal || this.node.contains(target)) {
return;
}
toggleMenu();
}
};
render() {
return (
<section ref={node => (this.node = node)}>
<p>
<button onClick={this.openModal}>open modal</button>
</p>
<p>
<button onClick={this.openModal}>open modal</button>
</p>
<p>
<button onClick={this.openModal}>open modal</button>
</p>
</section>
);
}
}
Run Code Online (Sandbox Code Playgroud)
从菜单中,我们可以通过单击菜单内的按钮打开一个模态。我们可以通过两种方式关闭模态:通过单击模态内的关闭模态按钮,或单击模态外的 bakcdrop/overlay:
class Modal extends Component {
hideModal = () => {
this.props.hideModal();
};
onOverlayClick = ({ target, currentTarget }) => {
if (target === currentTarget) {
this.hideModal();
}
};
render() {
return (
<div className="modal-container" onClick={this.onOverlayClick}>
<div className="modal">
<button onClick={this.hideModal}>close modal</button>
</div>
</div>
);
}
}
Run Code Online (Sandbox Code Playgroud)
现在,当菜单和模态打开时,在关闭模态单击或模态叠加单击时我只想关闭模态,菜单应该仍然打开。仅在第二次单击时(模式关闭时)。乍一看,它看起来非常清晰和简单,这种情况应该是造成这种情况的原因:
if (displayModal || this.node.contains(target)) {
return;
}
Run Code Online (Sandbox Code Playgroud)
如果 displayModal 是true,什么都不应该发生。I ts 不起作用,因为在我的情况下,当您单击关闭模式按钮或覆盖时,hideModal将比 完成得更快toggleMenu,而当我们调用handleClickOutsidedisplayModal 时,将已经有false.
开始时带有打开菜单和模态的完整测试用例:
这会有点长,因为我最近调查了类似的问题。如果您不想阅读所有内容,只需查看解决方案即可。
我想到了两个解决方案 - 第一个是简单的修复,第二个更干净,但需要额外的单击处理程序组件。
1.) 轻松修复
在 中Modal.js onOverlayClick,stopImmediatePropagation像这样添加:
onOverlayClick = e => {
// this is to stop click propagation in the react event system
e.stopPropagation();
// this is to stop click propagation to the native document click
// listener in Menu.js
e.nativeEvent.stopImmediatePropagation();
if (e.target === e.currentTarget) {
this.hideModal();
}
};
Run Code Online (Sandbox Code Playgroud)
在 上document,注册了两个点击侦听器:a) 第一个是 React 的顶级侦听器 b) 中的点击侦听器Menu.js。这样e.nativeEvent你就得到了 React 包装的原生 DOM 事件。stopImmediatePropagation当您只想关闭模式时,将取消第二个侦听器 - 并阻止关闭菜单。您可以在解释下阅读更多内容。
2.) 干净的
有了这个解决方案,您只需使用event.stopPropagation. 所有事件处理(包括外部点击处理程序)都是由 React 完成的,因此您不必再使用document.addEventListener("click",...)。下面click-handler.js只是一些代理,它捕获顶层的所有点击事件,并将它们在 React 事件系统中转发到您注册的组件。
创造click-handler.jsx:
import React from "react";
export const clickListenerApi = { addClickListener, removeClickListener };
export const ClickHandler = ({ children }) => {
return (
<div
// span click handler over the whole viewport to catch all clicks
style={{ minHeight: "100vh" }}
onClick={e => {
clickListeners.forEach(cb => cb(e));
}}
>
{children}
</div>
);
};
// state of registered click listeners
let clickListeners = [];
function addClickListener(cb) {
clickListeners.push(cb);
}
function removeClickListener(cb) {
clickListeners = clickListeners.filter(l => l !== cb);
}
Run Code Online (Sandbox Code Playgroud)
菜单.js:
class Menu extends Component {
componentDidMount() {
clickListenerApi.addClickListener(this.handleClickOutside);
}
componentWillUnmount() {
clickListenerApi.removeClickListener(this.handleClickOutside);
}
openModal = e => {
// This click shall not close the menu,
// so don't propagate the event to our clickListener API.
e.stopPropagation();
const { showModal } = this.props;
showModal();
};
render() {... }
}
Run Code Online (Sandbox Code Playgroud)
索引.js:
const App = () => (
<Provider store={store}>
<ClickHandler>
<Page />
</ClickHandler>
</Provider>
);
Run Code Online (Sandbox Code Playgroud)
当您同时打开模式对话框和菜单并在模式外部单击一次时,使用当前代码,行为是正确的 - 两个元素都关闭。这是因为 DOM 中document已经收到了单击事件并准备好调用handleClickOutside中的单击处理程序Menu。所以你没有机会再通过回调e.stopPropagation()来 取消它onOverlayClickModal。
为了理解两个点击事件触发的顺序,我们必须理解 React 有自己的合成事件处理系统(1、2 )。这里的要点是 React 使用顶级事件委托并在 DOM 中为所有事件类型添加一个侦听器。document
假设您<button id="foo" onClick={...}>Click it</button>在 DOM 中的某个位置有一个按钮。当您单击该按钮时,它会在浏览器中触发常规单击事件,该事件会document不断向上冒泡,直到到达 DOM 根。React 使用其单个侦听器捕获此点击事件document,然后再次在内部遍历其虚拟 DOM(类似于本机 DOM 的捕获和冒泡阶段)并收集您onClick={...}在组件中设置的所有相关点击回调。onClick因此稍后将找到并调用您的按钮。
这是有趣的部分:当 React 处理点击事件(现在是合成的 React 事件)时,原生点击事件已经在 DOM 中经历了完整的捕获/冒泡周期,并且在原生 DOM 中不存在不再了!这就是为什么在组件的 JSX 中混合使用本机单击处理程序 ( document.addEventListener) 和 ReactonEvent属性有时会很难处理且难以预测。React 事件处理程序应该始终是首选。
阅读链接:
希望能帮助到你。
| 归档时间: |
|
| 查看次数: |
6625 次 |
| 最近记录: |