我将Material-UI ClickAwayListener组件与使用react-router的现有代码结合使用。该按钮位于<ClickAwayListener> ... </ClickAwayListener>
之外,因此我希望onClickAway
在导航到其他路线之前会触发。但这不是
下面是我的代码的复制品,在某种程度上证明了我的意思
function Component(){
const handleClickAway = () => {
// Do something here
}
return (
<>
<button>
<Link to="/other-route">Click here </Link>
</button>
// Some other element here
<ClickAwayListener onClickAway={handleClickAway}>
<div>
// Content
</div>
</ClickAwayListener>
</>
)
}
因此,如果我点击<ClickAwayListener>
和<button>
之外的任何位置,就会触发handleClickAway
,但是如果我点击<button>
,其中包含与其他路线类似的内容不是。
我尝试查看ClickAwayListener
的源代码,并且我相信,此part负责检测点击
React.useEffect(() => {
if (mouseEvent !== false) {
const mappedMouseEvent = mapEventPropToEvent(mouseEvent);
const doc = ownerDocument(nodeRef.current);
doc.addEventListener(mappedMouseEvent, handleClickAway);
return () => {
doc.removeEventListener(mappedMouseEvent, handleClickAway);
};
}
return undefined;
}, [handleClickAway, mouseEvent]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
据我所知,这部分将首先在安装/重新渲染组件时将事件侦听器添加到click事件,并将在组件卸载之前删除该侦听器(useEffect()
的默认行为)。但是,如果是这种情况,那么在涉及到在ClickAwayListener
区域之外单击的任何事件导致组件卸载之前,应该触发onClickAway
侦听器,因为该侦听器仍附加到click事件上。
简而言之,这是我期望的行为:
单击按钮-> onClickAway
射击->卸下组件->转到新路线->运行useEffect()的清理代码->删除侦听器
但这是到目前为止发生的事情
单击按钮->组件卸载->转到新路径->运行useEffect()的清理代码->删除监听器
有人可以帮我解释为什么会这样吗?
答案 0 :(得分:1)
ClickAwayListener
组件通过将事件侦听器附加到document
来工作,当触发鼠标事件时,仅当鼠标事件不在元素内时才触发onClickAway
。
react-router-dom
中的Link
组件本质上是这样的:
<a onClick={() => navigate()}>click</a>
单击链接并调用navigate()
时,React会卸载当前组件,并在下一帧中安装下一页组件。但是事实是,document
处理程序仅在下一次重新渲染后进行处理,此时,ClickAwayListener
中的事件处理程序由于已卸载而已被删除。 ,所以什么也没叫。
可以通过等到document
的处理程序被调用后等待下一次重新渲染来解决问题。
<button
onClick={() => {
setTimeout(() => {
history.push("/2");
});
}}
>
答案 1 :(得分:0)
我可以通过使用以下功能代替材料用户界面的ClickAwayListener:https://github.com/streamich/react-use/blob/master/src/useClickAway.ts
解决此问题import { RefObject, useEffect, useRef } from 'react';
import { off, on } from './util';
const defaultEvents = ['mousedown', 'touchstart'];
const useClickAway = <E extends Event = Event>(
ref: RefObject<HTMLElement | null>,
onClickAway: (event: E) => void,
events: string[] = defaultEvents
) => {
const savedCallback = useRef(onClickAway);
useEffect(() => {
savedCallback.current = onClickAway;
}, [onClickAway]);
useEffect(() => {
const handler = (event) => {
const { current: el } = ref;
el && !el.contains(event.target) && savedCallback.current(event);
};
for (const eventName of events) {
on(document, eventName, handler);
}
return () => {
for (const eventName of events) {
off(document, eventName, handler);
}
};
}, [events, ref]);
};
export default useClickAway;
我不想将库作为依赖项导入,所以我创建了此代码的钩子并将util函数复制到其中
const on = (obj: any, ...args: any[]) => obj.addEventListener(...args);
const off = (obj: any, ...args: any[]) => obj.removeEventListener(...args);
此处提供了一个使用中的钩子示例:https://streamich.github.io/react-use/?path=/story/ui-useclickaway--demo
答案 2 :(得分:0)
与您的问题相关的issue已于2019年9月在Material UI repository中打开,其中Clickawaylistener不会同时激活按钮和链接。但是,直到2020年5月,这个问题似乎仍未得到充分解决。我建议在存储库中打开一个新问题,以确认这不是与按钮相关的错误。
同时,作为一种潜在的解决方法,根据您的用例,您可以将<Link>
或<Button>
组件与onClick
道具一起使用:
import React from "react";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import { Link, useHistory } from "react-router-dom";
import Button from "@material-ui/core/Button";
export default function Home() {
let history = useHistory();
const handleClickAway = () => {
console.log("Clicked Away");
};
function handleClick() {
handleClickAway();
history.push("/about");
}
return (
<div>
<Link to="/about" onClick={handleClickAway}>
Link
</Link>
<Button type="button" onClick={handleClick}>
Button
</Button>
<ClickAwayListener onClickAway={handleClickAway}>
<div>Home Page</div>
</ClickAwayListener>
</div>
);
}
我还在Codesandbox上包含了一个工作示例。