javascript - React - 如何检测是否单击了子组件
问题描述
请做:
- 访问该站点并单击右上角的汉堡包图标。侧导航应该打开。
- 再次执行相同的操作,您将看到我当前遇到的问题。
侧面导航没有正确关闭,因为我有两个相互冲突的功能正在运行。
第一个冲突函数是onClick
实际汉堡包组件中的切换函数,它切换关联的上下文状态。
第二个冲突功能由侧导航面板组件使用。它绑定到一个钩子,该钩子使用它ref
来检查用户是否单击了组件的内部或外部。
这些功能一直有效,直到用户在侧导航打开时单击汉堡菜单。汉堡包在技术上位于侧导航组件之外,但覆盖在其顶部,因此点击不会注册为外部。这导致两个函数一个接一个地触发。“点击外部”钩子触发并将侧导航上下文设置为假,关闭它。然后触发汉堡切换功能,发现上下文设置为 false 并将其更改回 true。因此立即关闭然后重新打开侧导航。
我尝试的解决方案如下所示。我试图ref
在汉堡包子组件中分配一个并将其传递给侧导航父级。我想尝试比较从useClickedOutside
给toggleRef
定到父级但汉堡包组件返回的回调,然后如果它们匹配则什么也不做。我希望,这将使该交互中的一个功能失效。不完全确定这是尝试实现此类目标的最佳方式。
也许我可以得到侧面导航边界框,然后检查点击坐标是否落在其中?
//SideNav.jsx
const SideToggle = ({ sideNavToggle, sideIsOpen, setToggleRef }) => {
useEffect(() => {
const toggleRef = React.createRef();
setToggleRef(toggleRef);
}, []);
return (
<Toggle onClick={sideNavToggle}>
<Bars>
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
</Bars>
</Toggle>
);
};
export default function SideNav() {
const [toggleRef, setToggleRef] = useState(null);
const { sideIsOpen, sideNavToggle, setSideIsOpen } = useContext(
NavigationContext
);
const handlers = useSwipeable({
onSwipedRight: () => {
sideNavToggle();
},
preventDefaultTouchmoveEvent: true,
trackMouse: true,
});
const sideRef = React.createRef();
useClickedOutside(sideRef, (callback) => {
if (callback) {
if (callback === toggleRef) {
return null;
} else setSideIsOpen(false);
}
});
return (
<>
<SideToggle
sideIsOpen={sideIsOpen}
sideNavToggle={sideNavToggle}
setToggleRef={setToggleRef}
/>
<div ref={sideRef}>
<Side sideIsOpen={sideIsOpen} {...handlers}>
<Section>
<Container>
<Col xs={12}>{/* <Menu /> */}</Col>
</Container>
</Section>
</Side>
</div>
</>
);
}
上面使用的useClickedOutside
钩子。
//use-clicked-outside.js
export default function useClickedOutside(ref, callback) {
useEffect(() => {
function handleClickOutside(event) {
if (ref.current && !ref.current.contains(event.target)) {
callback(event.target);
} else return null;
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [ref]);
}
编辑
通过将切换组件移动到侧面导航组件内,设法解决了部分问题。它现在有自己的问题,因为当侧面导航道具更改时,汉堡包组件会完全重新渲染。试图弄清楚如何在这样的钩子场景中停止汉堡包重新渲染。我一直在阅读这React.memo
可能是答案,但目前还不确定如何实现它。
//SideNav.jsx
export default function SideNav() {
const { sideIsOpen, sideNavClose, sideNavOpen } = useContext(
NavigationContext
);
const handlers = useSwipeable({
onSwipedRight: () => {
sideNavClose();
},
preventDefaultTouchmoveEvent: true,
trackMouse: true,
});
const sideRef = React.createRef();
useClickedOutside(sideRef, (callback) => {
if (callback) {
sideNavClose();
}
});
const SideToggle = () => {
const handleClick = () => {
if (!sideIsOpen) {
sideNavOpen();
}
};
return (
<Toggle onClick={() => handleClick()}>
<Bars>
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
<Bar sideIsOpen={sideIsOpen} />
</Bars>
</Toggle>
);
};
return (
<>
<SideToggle />
<div ref={sideRef}>
<Side sideIsOpen={sideIsOpen} {...handlers}>
<Section>
<Container>
<Col xs={12}>{/* <Menu /> */}</Col>
</Container>
</Section>
</Side>
</div>
</>
);
}
解决方案
我建议使用这个包:https ://github.com/airbnb/react-outside-click-handler 。它处理您提到的一些边缘情况,如果您对细节感兴趣,可以查看它们的源代码。
然后您需要保留有关侧边栏是否在状态下打开的信息并将其传递给受影响的组件,有点像这样(这样您还可以根据工具栏状态更改站点在其他地方的外观,例如禁用滚动条等):
function Site() {
const [isOpened, setToolbarState] = React.useState(false);
return (
<React.Fragment>
<Toolbar isOpened={isOpened} setToolbarState={setToolbarState} />
{isOpened ? (<div>draw some background if needed</div>) : null}
<RestOfTheSite />
</React.Fragment>
);
}
function Toolbar(props) {
const setIsClosed = React.useCallback(function () {
props.setToolbarState(false);
}, [props.setToolbarState]);
const setIsOpened = React.useCallback(function () {
props.setToolbarState(true);
}, [props.setToolbarState]);
return (
<OutsideClickHandler onOutsideClick={setIsClosed}>
<div className="toolbar">
<button onClick={setIsOpened}>open</button>
...
{props.isOpened ? (<div>render links, etc here</div>) : null}
</div>
</OutsideClickHandler>
);
}
这样你就不需要过多地处理 refs 和 handlers 了。
推荐阅读
- python - 数组字段上的 MongoDB find_one 每次都返回文档,即使没有匹配
- ruby-on-rails - 如何在 ruby 中允许和验证参数?
- python - 未捕获 pyodbc 异常
- python - 代码在 chrome 中的 Jupyter notebook 上运行,不要在相同环境的 VSCode 中运行
- python - 不再支持 Python 2.7 时的 GraphLab 安装
- javascript - p5js mousePressed 在 2D 网格上
- r - read.csv - 根据是否存在重复值来分离存储在 .csv 中的信息
- java - java中声明的范围
- android - 如何测试具有本机依赖的 npm 库
- javascript - ESLint 在配置文件中定义文件夹。(忽略所有...,仅包括...)