【发布时间】:2021-03-10 06:51:10
【问题描述】:
我正在为我的应用程序使用 Material-UI ClickAwayListener 和 react-router。我遇到的错误是,当ClickAwayListener 的回调被执行时,它会在useEffect 运行中途停止,然后它会恢复运行。回调不应出现此行为。回调应该在useEffect 可以运行之前完全执行。下面是我为演示问题而创建的代码和代码的this is the demo
import React, { useEffect, useState } from "react";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useHistory,
useParams
} from "react-router-dom";
export default function BasicExample() {
return (
<Router>
<div>
<Switch>
<Route exact path="/">
<ButtonPage />
</Route>
{/*Main focus route here*/ }
<Route path="/:routeId">
<Home />
</Route>
</Switch>
</div>
</Router>
);
}
// Main focus here
function Home() {
const history = useHistory();
const { routeId } = useParams();
const [count, setCount] = useState(0);
const handleClick1 = () => {
history.push("/route1");
};
const handleClick2 = () => {
history.push("/route2");
};
// useEffect run on re-render and re-mount
useEffect(() => {
console.log("Re-render or remount");
});
useEffect(() => {
console.log("Run on route change from inside useEffect");
}, [routeId]);
const handleClickAway = () => {
console.log("First line in handle click away");
setCount(count + 1);
console.log("Second line in handle click away");
};
return (
<div>
<button onClick={handleClick1}>Route 1 </button>
<button onClick={handleClick2}>Route 2 </button>
<ClickAwayListener onClickAway={handleClickAway}>
<div style={{ height: 100, width: 100, backgroundColor: "green" }}>
Hello here
</div>
</ClickAwayListener>
</div>
);
}
// Just a component such that home route can navigate
// Not important for question
function ButtonPage() {
const history = useHistory();
const handleClick1 = () => {
history.push("/route1");
};
const handleClick2 = () => {
history.push("/route2");
};
return (
<div>
<button onClick={handleClick1}>Route 1 </button>
<button onClick={handleClick2}>Route 2 </button>
</div>
);
}
具体来说,当我在ClickAwayListener 之外点击时,handleClickAway 正常运行并且日志消息是
First line in handle click away
Second line in handle click away
Re-render or remount
直到我选择点击导航到其他路线的button。事情变得很奇怪:handleClickAway 运行第一行日志记录,然后useEffect 运行并打印它的日志记录,然后handleClickAway 恢复并打印它的第二行。所以如果我这样做,这就是日志记录
First line in handle click away
Re-render or remount
Run on route change from inside useEffect
Second line in handle click away
在对此错误进行一些测试后,我发现导致此错误的原因是handleClickAway 中的setCount。如果我删除此行,函数 handleClickAway 将在所有情况下按预期运行。我的结论是,更改组件状态,或者我应该说,执行任何导致组件在 handleClickAway 内重新渲染的操作与路由导航相结合都会导致此错误。
这种行为很奇怪,因为据我所知,正常的、与 Promise 无关的回调无法像这样中途停止。我猜ClickAwayListener 不知何故将handleClickAway 变成了 Promise 之类的东西。但即便如此,它也没有理由停在setCount 并让useEffect 运行?
谁能给我解释一下?
编辑 1:正如@Rex Pan 在此答案中指出的那样,handleClickAway 似乎在其中运行 useEffect。这是我通过阅读跟踪堆栈可以得出的唯一结论。但是这种行为只有在用户点击导航到不同路线的链接时才会发生。单击旁边的其他区域不会导致此错误。有人可以解释一下为什么以及如何发生这种情况吗?
编辑 2:阅读@Rex Pan 的回答后,我做了更多调查。在我看来,他的回答是正确的,但只适用于ClickAwayListener 回调。所以下面的代码会产生和上面一样的“bug”
export default function ClickAway() {
const classes = useStyles();
const [count, setCount] = React.useState(0);
useEffect(() => {
console.log("Test in useEffect");
});
const handleClickAway = () => {
console.log("Count is " + count);
setCount(count + 1);
setCount(count + 10);
console.log("Count is " + count);
};
return (
<ClickAwayListener onClickAway={handleClickAway}>
<div className={classes.root}>Click away</div>
</ClickAwayListener>
);
}
但是,他的解释在其他情况下不起作用,例如下面的代码
export default function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Run inside useEffect");
});
const handleClick = () => {
console.log("First line");
setCount(count + 1);
console.log("Second line");
setCount(count + 100);
console.log("Third line");
};
return (
<>
<div>Count is: {count} </div>
<button onClick={handleClick}>Click</button>
</>
);
}
我正在尝试阅读ClickAwayListner 的源代码,但到目前为止还没有找到导致此行为的位置。有人能指出这一点吗?
【问题讨论】:
标签: javascript reactjs callback react-router material-ui